import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import type { MetaApiClient } from './client/meta-client.js';
import type { ToolCategory, LoadTier, ToolAnnotations } from './types/meta-api.js';

/**
 * Tool definition with metadata
 */
export interface ToolDefinition {
  name: string;
  description: string;
  inputSchema: z.ZodType<unknown>;
  annotations?: ToolAnnotations;
  _meta?: {
    labels: {
      category: string;
      access: "read" | "write" | "delete";
      complexity: "simple" | "complex" | "batch";
    };
  };
  handler: (args: Record<string, unknown>) => Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }>;
}

/**
 * Module registry for lazy loading
 */
interface ToolModule {
  category: ToolCategory;
  tier: LoadTier;
  loaded: boolean;
  register: (registry: ToolRegistry) => Promise<void> | void;
}

export class ToolRegistry {
  private tools = new Map<string, ToolDefinition>();
  private client: MetaApiClient;

  constructor(client: MetaApiClient) {
    this.client = client;
  }

  /**
   * Register a tool
   */
  registerTool(tool: ToolDefinition): void {
    this.tools.set(tool.name, tool);
    console.error(`[MCP] Registered tool: ${tool.name}`);
  }

  /**
   * Get all registered tools
   */
  getTools(): ToolDefinition[] {
    return Array.from(this.tools.values());
  }

  /**
   * Get a specific tool
   */
  getTool(name: string): ToolDefinition | undefined {
    return this.tools.get(name);
  }

  /**
   * Get the API client
   */
  getClient(): MetaApiClient {
    return this.client;
  }
}

export class ModuleRegistry {
  private modules = new Map<ToolCategory, ToolModule>();
  private toolRegistry: ToolRegistry;

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

  /**
   * Register a tool module
   */
  registerModule(module: ToolModule): void {
    this.modules.set(module.category, module);
  }

  /**
   * Load a module if not already loaded
   */
  async loadModule(category: ToolCategory): Promise<void> {
    const module = this.modules.get(category);
    
    if (!module) {
      throw new Error(`Module ${category} not registered`);
    }

    if (module.loaded) {
      return;
    }

    console.error(`[MCP] Loading module: ${category}`);
    await module.register(this.toolRegistry);
    module.loaded = true;
    console.error(`[MCP] Module loaded: ${category}`);
  }

  /**
   * Get module status
   */
  getModuleStatus(): Record<string, { tier: LoadTier; loaded: boolean }> {
    const status: Record<string, { tier: LoadTier; loaded: boolean }> = {};
    
    for (const [category, module] of this.modules.entries()) {
      status[category] = {
        tier: module.tier,
        loaded: module.loaded,
      };
    }
    
    return status;
  }

  /**
   * Check if a module is loaded
   */
  isModuleLoaded(category: ToolCategory): boolean {
    return this.modules.get(category)?.loaded || false;
  }

  /**
   * Get tool registry
   */
  getToolRegistry(): ToolRegistry {
    return this.toolRegistry;
  }
}

/**
 * Create gateway tool for lazy-loaded advanced modules
 */
export function createGatewayTool(
  category: ToolCategory,
  registry: ModuleRegistry,
  description: string
): ToolDefinition {
  return {
    name: `${category}_tools`,
    description,
    inputSchema: z.object({}),
    annotations: {
      title: `Load ${category} module`,
      readOnlyHint: true,
      openWorldHint: false,
    },
    handler: async () => {
      // Load the module if not loaded
      if (!registry.isModuleLoaded(category)) {
        await registry.loadModule(category);
        
        return {
          content: [{
            type: 'text',
            text: `Module '${category}' has been loaded. Available tools are now registered. Please list tools again to see them.`,
          }],
        };
      }

      return {
        content: [{
          type: 'text',
          text: `Module '${category}' is already loaded.`,
        }],
      };
    },
  };
}

/**
 * Create and configure the MCP server
 */
export function createMcpServer(toolRegistry: ToolRegistry): Server {
  const server = new Server(
    {
      name: '@clawdbot/meta-ads-mcp',
      version: '1.0.0',
    },
    {
      capabilities: {
        tools: {},
      },
    }
  );

  // Handle tools/list
  server.setRequestHandler('tools/list' as never, async () => {
    const tools = toolRegistry.getTools();
    
    return {
      tools: tools.map(tool => {
        // Convert Zod schema to JSON Schema
        const inputSchema = zodToJsonSchema(tool.inputSchema);
        
        return {
          name: tool.name,
          description: tool.description,
          inputSchema,
          _meta: tool._meta,
          annotations: tool.annotations,
        };
      }),
    };
  });

  // Handle tools/call
  server.setRequestHandler('tools/call' as never, async (request: { params: { name: string; arguments: Record<string, unknown> } }) => {
    const { name, arguments: args } = request.params;
    
    const tool = toolRegistry.getTool(name);
    if (!tool) {
      return {
        content: [{
          type: 'text',
          text: `Unknown tool: ${name}`,
        }],
        isError: true,
      };
    }

    try {
      // Validate input
      const validatedArgs = tool.inputSchema.parse(args);
      
      // Execute handler
      return await tool.handler(validatedArgs as Record<string, unknown>);
    } catch (error) {
      return {
        content: [{
          type: 'text',
          text: `Error: ${error instanceof Error ? error.message : String(error)}`,
        }],
        isError: true,
      };
    }
  });

  return server;
}

/**
 * Convert Zod schema to JSON Schema (simplified)
 */
function zodToJsonSchema(schema: z.ZodType<unknown>): Record<string, unknown> {
  // This is a simplified conversion. For production, use @anatine/zod-to-openapi or similar
  if (schema instanceof z.ZodObject) {
    const shape = (schema as z.ZodObject<z.ZodRawShape>).shape;
    const properties: Record<string, unknown> = {};
    const required: string[] = [];

    for (const [key, value] of Object.entries(shape)) {
      if (value instanceof z.ZodString) {
        properties[key] = {
          type: 'string',
          description: value.description,
        };
      } else if (value instanceof z.ZodNumber) {
        properties[key] = {
          type: 'number',
          description: value.description,
        };
      } else if (value instanceof z.ZodBoolean) {
        properties[key] = {
          type: 'boolean',
          description: value.description,
        };
      } else if (value instanceof z.ZodArray) {
        properties[key] = {
          type: 'array',
          description: value.description,
          items: {},
        };
      } else if (value instanceof z.ZodEnum) {
        properties[key] = {
          type: 'string',
          enum: value.options,
          description: value.description,
        };
      } else {
        properties[key] = {
          type: 'object',
          description: value.description,
        };
      }

      if (!value.isOptional()) {
        required.push(key);
      }
    }

    return {
      type: 'object',
      properties,
      required,
    };
  }

  return { type: 'object' };
}

/**
 * Connect server to stdio transport
 */
export async function connectServer(server: Server): Promise<void> {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('[MCP] Meta Ads MCP server running on stdio');
}
