import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { ApiClient } from './api-client.js';

// ---------- Types ----------

/** Standard tool definition — maps directly to an API endpoint */
export interface ToolDef {
  name: string;
  description: string;
  inputSchema: Record<string, z.ZodTypeAny>;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  path: string; // e.g., '/api/campaigns/:id'
  tier: 1 | 2;
}

/** Composite tool — custom handler that chains multiple API calls */
export interface CompositeToolDef {
  name: string;
  description: string;
  inputSchema: Record<string, z.ZodTypeAny>;
  handler: (args: Record<string, unknown>, apiClient: ApiClient) => Promise<string>;
  tier: 3;
}

export type AnyToolDef = ToolDef | CompositeToolDef;

/** Tool pack metadata */
export interface ToolPack {
  name: string;
  description: string;
  tools: AnyToolDef[];
}

// ---------- Handler Factory ----------

/**
 * Create a generic handler for a standard ToolDef.
 * Substitutes path params (`:id` → `args.id`), then sends remaining
 * fields as query params (GET/DELETE) or body (POST/PUT/PATCH).
 */
function createHandler(tool: ToolDef, apiClient: ApiClient) {
  return async (args: Record<string, unknown>): Promise<string> => {
    // Clone args so we can remove path params without mutating
    const remaining = { ...args };

    // Substitute path parameters
    let path = tool.path;
    const pathParamMatches = tool.path.match(/:(\w+)/g) || [];
    for (const match of pathParamMatches) {
      const key = match.slice(1);
      if (remaining[key] !== undefined) {
        path = path.replace(match, String(remaining[key]));
        delete remaining[key];
      } else {
        throw new Error(`Missing required path parameter: ${key}`);
      }
    }

    // Strip undefined values from remaining params
    const cleanParams: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(remaining)) {
      if (v !== undefined) cleanParams[k] = v;
    }

    const hasParams = Object.keys(cleanParams).length > 0;
    const result = await apiClient.request(
      tool.method,
      path,
      hasParams ? cleanParams : undefined
    );

    return JSON.stringify(result, null, 2);
  };
}

// ---------- Pack Manager ----------

export class ToolRegistry {
  private server: McpServer;
  private apiClient: ApiClient;
  private activePacks = new Set<string>();
  private availablePacks = new Map<string, ToolPack>();
  private registeredTools = new Set<string>();

  constructor(server: McpServer, apiClient: ApiClient) {
    this.server = server;
    this.apiClient = apiClient;
  }

  /** Register a batch of tools (Tier 1 core tools) */
  registerTools(tools: AnyToolDef[]): void {
    for (const tool of tools) {
      this.registerSingleTool(tool);
    }
  }

  /** Register a tool pack (available for on-demand activation) */
  registerPack(pack: ToolPack): void {
    this.availablePacks.set(pack.name, pack);
  }

  /** Activate a tool pack — registers its tools and notifies the client */
  enablePack(packName: string): { success: boolean; message: string } {
    if (this.activePacks.has(packName)) {
      return { success: true, message: `Pack "${packName}" is already active` };
    }

    const pack = this.availablePacks.get(packName);
    if (!pack) {
      const available = Array.from(this.availablePacks.keys()).join(', ');
      return {
        success: false,
        message: `Unknown pack "${packName}". Available: ${available}`,
      };
    }

    for (const tool of pack.tools) {
      this.registerSingleTool(tool);
    }

    this.activePacks.add(packName);
    return {
      success: true,
      message: `Enabled "${packName}" pack — ${pack.tools.length} tools added: ${pack.tools.map((t) => t.name).join(', ')}`,
    };
  }

  /** Get status of all available packs */
  getPackStatus(): Array<{ name: string; description: string; active: boolean; toolCount: number }> {
    return Array.from(this.availablePacks.entries()).map(([name, pack]) => ({
      name,
      description: pack.description,
      active: this.activePacks.has(name),
      toolCount: pack.tools.length,
    }));
  }

  // ---------- Internal ----------

  private registerSingleTool(tool: AnyToolDef): void {
    if (this.registeredTools.has(tool.name)) return; // Prevent duplicate registration

    if (tool.tier === 3) {
      // Composite tool — custom handler
      const composite = tool as CompositeToolDef;
      const apiClient = this.apiClient;
      this.server.tool(
        composite.name,
        composite.description,
        composite.inputSchema,
        async (args) => ({
          content: [{ type: 'text' as const, text: await composite.handler(args as Record<string, unknown>, apiClient) }],
        })
      );
    } else {
      // Standard tool — generic HTTP handler
      const standard = tool as ToolDef;
      const handler = createHandler(standard, this.apiClient);
      this.server.tool(
        standard.name,
        standard.description,
        standard.inputSchema,
        async (args) => ({
          content: [{ type: 'text' as const, text: await handler(args as Record<string, unknown>) }],
        })
      );
    }

    this.registeredTools.add(tool.name);
  }
}
