import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
  ErrorCode,
  McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { StripeClient } from './clients/stripe.js';

export interface StripeServerConfig {
  apiKey: string;
  webhookSecret?: string;
}

/**
 * Tool module interface for lazy loading
 */
interface ToolModule {
  name: string;
  description: string;
  inputSchema: any;
  handler: (client: StripeClient, args: any) => Promise<any>;
}

/**
 * Lazy-loaded tool registry
 * Tools are loaded on-demand to reduce startup time and memory footprint
 */
const TOOL_MODULES: Record<string, () => Promise<ToolModule[]>> = {
  'customers': async () => (await import('./tools/customers.js')).default,
  'charges': async () => (await import('./tools/charges.js')).default,
  'payment-intents': async () => (await import('./tools/payment-intents.js')).default,
  'payment-methods': async () => (await import('./tools/payment-methods.js')).default,
  'refunds': async () => (await import('./tools/refunds.js')).default,
  'disputes': async () => (await import('./tools/disputes.js')).default,
  'subscriptions': async () => (await import('./tools/subscriptions.js')).default,
  'invoices': async () => (await import('./tools/invoices.js')).default,
  'products': async () => (await import('./tools/products.js')).default,
  'prices': async () => (await import('./tools/prices.js')).default,
  'payouts': async () => (await import('./tools/payouts.js')).default,
  'balance': async () => (await import('./tools/balance.js')).default,
  'events': async () => (await import('./tools/events.js')).default,
  'webhooks': async () => (await import('./tools/webhooks.js')).default,
};

/**
 * Resource definitions for UI apps
 */
const RESOURCES = [
  {
    uri: 'stripe://dashboard',
    name: 'Stripe Dashboard',
    description: 'Overview of account activity and key metrics',
    mimeType: 'application/json',
  },
  {
    uri: 'stripe://customers',
    name: 'Customer List',
    description: 'List of all customers',
    mimeType: 'application/json',
  },
  {
    uri: 'stripe://payments/recent',
    name: 'Recent Payments',
    description: 'Recent payment intents and charges',
    mimeType: 'application/json',
  },
  {
    uri: 'stripe://subscriptions/active',
    name: 'Active Subscriptions',
    description: 'Currently active subscriptions',
    mimeType: 'application/json',
  },
  {
    uri: 'stripe://invoices/unpaid',
    name: 'Unpaid Invoices',
    description: 'Invoices that are unpaid or overdue',
    mimeType: 'application/json',
  },
];

export class StripeServer {
  private server: Server;
  private client: StripeClient;
  private toolCache: Map<string, ToolModule[]> = new Map();

  constructor(config: StripeServerConfig) {
    this.client = new StripeClient({ apiKey: config.apiKey });
    
    this.server = new Server(
      {
        name: '@mcpengine/stripe',
        version: '1.0.0',
      },
      {
        capabilities: {
          tools: {},
          resources: {},
        },
      }
    );

    this.setupHandlers();
  }

  private setupHandlers(): void {
    // List available tools
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      const tools: any[] = [];

      // Load all tool modules to get their definitions
      for (const [moduleName, loader] of Object.entries(TOOL_MODULES)) {
        try {
          const moduleTools = await this.getToolModule(moduleName);
          for (const tool of moduleTools) {
            tools.push({
              name: tool.name,
              description: tool.description,
              inputSchema: tool.inputSchema,
            });
          }
        } catch (error) {
          console.error(`Failed to load tool module ${moduleName}:`, error);
        }
      }

      return { tools };
    });

    // Handle tool calls
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;

      try {
        // Find the tool handler
        const handler = await this.findToolHandler(name);
        
        if (!handler) {
          throw new McpError(
            ErrorCode.MethodNotFound,
            `Tool not found: ${name}`
          );
        }

        // Execute the tool
        const result = await handler(this.client, args || {});

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (error) {
        if (error instanceof McpError) {
          throw error;
        }

        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        throw new McpError(
          ErrorCode.InternalError,
          `Tool execution failed: ${errorMessage}`
        );
      }
    });

    // List available resources
    this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
      return { resources: RESOURCES };
    });

    // Read resource data
    this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
      const { uri } = request.params;

      try {
        const content = await this.handleResourceRead(uri);
        
        return {
          contents: [
            {
              uri,
              mimeType: 'application/json',
              text: JSON.stringify(content, null, 2),
            },
          ],
        };
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        throw new McpError(
          ErrorCode.InternalError,
          `Failed to read resource: ${errorMessage}`
        );
      }
    });
  }

  /**
   * Get tool module (with caching)
   */
  private async getToolModule(moduleName: string): Promise<ToolModule[]> {
    if (this.toolCache.has(moduleName)) {
      return this.toolCache.get(moduleName)!;
    }

    const loader = TOOL_MODULES[moduleName];
    if (!loader) {
      throw new Error(`Tool module not found: ${moduleName}`);
    }

    const tools = await loader();
    this.toolCache.set(moduleName, tools);
    return tools;
  }

  /**
   * Find tool handler by name
   */
  private async findToolHandler(toolName: string): Promise<((client: StripeClient, args: any) => Promise<any>) | null> {
    for (const moduleName of Object.keys(TOOL_MODULES)) {
      const tools = await this.getToolModule(moduleName);
      const tool = tools.find(t => t.name === toolName);
      if (tool) {
        return tool.handler;
      }
    }
    return null;
  }

  /**
   * Handle resource read requests
   */
  private async handleResourceRead(uri: string): Promise<any> {
    // Parse the URI
    const url = new URL(uri);
    const path = url.pathname;

    switch (path) {
      case '/dashboard':
        return this.getDashboardData();
      
      case '/customers':
        return this.client.list('customers', { limit: 100 });
      
      case '/payments/recent':
        return this.getRecentPayments();
      
      case '/subscriptions/active':
        return this.client.list('subscriptions', { status: 'active', limit: 100 });
      
      case '/invoices/unpaid':
        return this.client.list('invoices', { status: 'open', limit: 100 });
      
      default:
        throw new Error(`Unknown resource path: ${path}`);
    }
  }

  /**
   * Get dashboard data (overview metrics)
   */
  private async getDashboardData(): Promise<any> {
    const [balance, recentCharges, recentCustomers] = await Promise.all([
      this.client.get('balance'),
      this.client.list('charges', { limit: 10 }),
      this.client.list('customers', { limit: 10 }),
    ]);

    return {
      balance,
      recent_charges: recentCharges,
      recent_customers: recentCustomers,
      timestamp: new Date().toISOString(),
    };
  }

  /**
   * Get recent payments
   */
  private async getRecentPayments(): Promise<any> {
    const [paymentIntents, charges] = await Promise.all([
      this.client.list('payment_intents', { limit: 50 }),
      this.client.list('charges', { limit: 50 }),
    ]);

    return {
      payment_intents: paymentIntents,
      charges,
      timestamp: new Date().toISOString(),
    };
  }

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

  /**
   * Get the underlying MCP server instance
   */
  getServer(): Server {
    return this.server;
  }

  /**
   * Close the server
   */
  async close(): Promise<void> {
    await this.server.close();
  }
}

export function createStripeServer(config: StripeServerConfig): StripeServer {
  return new StripeServer(config);
}
