/**
 * Capability Gateway MCP Server — exposes capability_list/load/run tools.
 *
 * This is a lightweight in-process MCP server injected into every session.
 * It lets Claude discover and activate capabilities on demand.
 */

import { z } from 'zod';
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
import type { McpSdkServerConfigWithInstance } from '@anthropic-ai/claude-agent-sdk';
import type { CapabilityGatewayOptions, CapabilityEntry, CapabilityRunner } from './types.js';
import { createOfficeSkillsMcpTools } from './office-skills-runner.js';
import { createFrontendSlidesInstructions } from './frontend-slides-runner.js';
import { createGenericInstructions } from './generic-instruction-runner.js';

type ToolResult = { content: Array<{ type: 'text'; text: string }>; isError?: boolean };

function textResult(text: string, isError = false): ToolResult {
  return { content: [{ type: 'text', text }], isError };
}

function errorResult(message: string): ToolResult {
  return textResult(message, true);
}

/** Track dynamically loaded capability MCP servers per session */
const loadedServers = new Map<string, Map<string, McpSdkServerConfigWithInstance>>();

export function createCapabilityGatewayMcpServer(options: CapabilityGatewayOptions): McpSdkServerConfigWithInstance {
  const { registry, getActiveSession, pluginsDir } = options;

  const STEP_NAMES = ['约束加载', '需求分析', '写实施计划', '逐任务执行', '集成验证', '代码优化', '总结沉淀'] as const;

  const listTool = tool(
    'capability_list',
    'List all available on-demand capabilities. '
    + 'Call this when you need to discover what extended capabilities are available '
    + '(office documents, presentations, QA testing, etc.). '
    + 'Search by keywords (exact OR match) or natural language (semantic match).',
    {
      keywords: z.array(z.string()).optional().describe('Keywords for exact OR matching against triggers/names (e.g., ["PPT", "Word"])'),
      natural_language: z.string().optional().describe('Natural language description for semantic matching when keywords miss (e.g., "我需要做季度销售PPT")'),
    },
    async (args: any) => {
      const keywords: string[] = args.keywords ?? [];
      const naturalLanguage: string = args.natural_language ?? '';

      // Step 1: Exact OR keyword matching
      let capabilities = keywords.length > 0
        ? registry.searchByKeywords(keywords)
        : registry.listCapabilities();

      // Step 2: If keyword match missed and we have natural language, try LLM semantic search
      if (capabilities.length === 0 && naturalLanguage.trim()) {
        const llmConfig = options.getLlmConfig?.();
        if (llmConfig) {
          const semanticIds = await registry.searchSemantic(naturalLanguage, keywords, llmConfig);
          if (semanticIds.length > 0) {
            // Map IDs back to CapabilityEntry
            const semanticCaps = semanticIds
              .map((id) => registry.getCapability(id))
              .filter((c): c is CapabilityEntry => c != null);
            // Also include user capability matches as synthetic entries
            for (const id of semanticIds) {
              if (!semanticCaps.find((c) => c.id === id)) {
                const userCap = registry.getUserCapability(id);
                if (userCap) {
                  semanticCaps.push({
                    id: userCap.id,
                    name: userCap.name,
                    description: userCap.pattern,
                    executionMode: 'instruction-inject',
                    triggers: userCap.shortcuts,
                    runnerModule: '',
                    pluginPath: '',
                    enabled: true,
                    version: '1.0.0',
                  });
                }
              }
            }
            capabilities = semanticCaps;
          }
        }
      }

      // Also include user capabilities that match keywords (always search both)
      if (keywords.length > 0) {
        const userCaps = registry.listUserCapabilities();
        for (const uc of userCaps) {
          if (capabilities.find((c) => c.id === uc.id)) continue;
          const haystack = [uc.id, uc.name, uc.pattern, ...uc.shortcuts].join(' ').toLowerCase();
          const matches = keywords.some((kw) => haystack.includes(kw.toLowerCase()));
          if (matches) {
            capabilities.push({
              id: uc.id,
              name: uc.name,
              description: uc.pattern,
              executionMode: 'instruction-inject',
              triggers: uc.shortcuts,
              runnerModule: '',
              pluginPath: '',
              enabled: true,
              version: '1.0.0',
            });
          }
        }
      }

      if (capabilities.length === 0) {
        return textResult(naturalLanguage || keywords.length > 0
          ? `No capabilities matching your request. Available capabilities may not cover this task.`
          : 'No capabilities available.');
      }

      const lines = capabilities.map((c) => {
        const triggerStr = c.triggers.slice(0, 5).join(', ');
        const usage = registry.getUsage(c.id);
        const usageNote = usage ? ` (used ${usage.count}x)` : '';
        return `- **${c.id}**: ${c.name} — ${c.description}${usageNote}\n  Triggers: ${triggerStr}\n  Mode: ${c.executionMode}`;
      });

      return textResult(
        `Available capabilities (${capabilities.length}):\n\n${lines.join('\n\n')}\n\n`
        + 'To activate a capability, call `capability_load` with the capability ID.',
      );
    },
  );

  const loadTool = tool(
    'capability_load',
    'Load and activate a capability by ID. '
    + 'This makes the capability\'s tools or instructions available in the current session. '
    + 'Call `capability_list` first to discover available capabilities.',
    {
      capability_id: z.string().describe('The capability ID to load (e.g., "office-skills")'),
      session_id: z.string().describe('Current session ID for dynamic MCP injection'),
    },
    async (args: any) => {
      const cap = registry.getCapability(args.capability_id);
      if (!cap) {
        return errorResult(`Unknown capability: "${args.capability_id}". Call capability_list to see available options.`);
      }
      if (!cap.enabled) {
        return errorResult(`Capability "${cap.name}" is currently disabled.`);
      }

      switch (cap.executionMode) {
        case 'mcp-dynamic':
          registry.recordUsage(cap.id, args.task_description, true);
          return await handleMcpDynamic(cap, args.session_id);
        case 'instruction-inject':
          registry.recordUsage(cap.id, args.task_description, true);
          return handleInstructionInject(cap);
        case 'cli-subprocess':
          return textResult(
            `Capability "${cap.name}" is available as a CLI command.\n`
            + `Runner module: ${cap.runnerModule}\n`
            + `Use capability_run to execute commands.`,
          );
        default:
          return errorResult(`Unknown execution mode: ${cap.executionMode}`);
      }
    },
  );

  const runTool = tool(
    'capability_run',
    'Execute a capability command directly. Use for simple execution tasks '
    + 'where full MCP tool exposure is not needed.',
    {
      capability_id: z.string().describe('The capability ID to execute'),
      command: z.string().describe('Sub-command to execute'),
    },
    async (args: any) => {
      const cap = registry.getCapability(args.capability_id);
      if (!cap) {
        return errorResult(`Unknown capability: "${args.capability_id}".`);
      }

      // For CLI-subprocess mode, delegate to runner
      if (cap.executionMode !== 'cli-subprocess') {
        return errorResult(
          `Capability "${cap.name}" uses ${cap.executionMode} mode. `
          + `Use capability_load instead of capability_run.`,
        );
      }

      try {
        const runner = loadRunner(cap, pluginsDir);
        if (!runner.execute) {
          return errorResult(`Capability "${cap.name}" does not support direct execution.`);
        }
        const result = await runner.execute(args.command, args.args ?? {});
        return textResult(result);
      } catch (e: any) {
        return errorResult(`Execution failed: ${e.message}`);
      }
    },
  );

  async function handleMcpDynamic(cap: CapabilityEntry, sessionId: string): Promise<ToolResult> {
    const session = getActiveSession(sessionId);
    if (!session) {
      return errorResult(`No active session found for ID: ${sessionId}`);
    }

    try {
      // Create runner-specific MCP tools
      const mcpTools = createRunnerMcpTools(cap, pluginsDir);
      const serverName = `capability-${cap.id}`;

      const capServer = createSdkMcpServer({
        name: serverName,
        version: cap.version,
        tools: mcpTools,
      });

      // Track loaded servers for this session
      if (!loadedServers.has(sessionId)) {
        loadedServers.set(sessionId, new Map());
      }
      const sessionServers = loadedServers.get(sessionId)!;

      // If already loaded, return existing info
      if (sessionServers.has(cap.id)) {
        return textResult(`Capability "${cap.name}" is already loaded and active.`);
      }

      sessionServers.set(cap.id, capServer);

      // Inject all loaded capability servers via setMcpServers
      const allServers: Record<string, McpSdkServerConfigWithInstance> = {};
      for (const [id, srv] of sessionServers) {
        allServers[`capability-${id}`] = srv;
      }

      const result = await session.setMcpServers(allServers);

      const toolNames = mcpTools.map((t: any) => t.name).join(', ');
      return textResult(
        `Capability "${cap.name}" loaded successfully.\n`
        + `Available tools: ${toolNames}\n`
        + `Servers added: ${result.added.join(', ') || '(already present)'}\n`
        + (Object.keys(result.errors).length > 0
          ? `Errors: ${JSON.stringify(result.errors)}\n`
          : '')
        + `You can now use these tools to complete the task.`,
      );
    } catch (e: any) {
      return errorResult(`Failed to load capability "${cap.name}": ${e.message}`);
    }
  }

  function handleInstructionInject(cap: CapabilityEntry): ToolResult {
    try {
      const instructions = createRunnerInstructions(cap, pluginsDir);
      if (!instructions) {
        return errorResult(`No instructions available for capability "${cap.name}".`);
      }
      return textResult(
        `Capability "${cap.name}" activated. Follow the instructions below:\n\n---\n${instructions}\n---\n`
        + `\nUse your existing tools (Bash, Write, Edit, etc.) to execute the workflow described above.`,
      );
    } catch (e: any) {
      return errorResult(`Failed to load instructions for "${cap.name}": ${e.message}`);
    }
  }

  const workflowUpdateTool = tool(
    'workflow_update',
    'Update dev-workflow progress. Call this when you enter a new step in the dev-workflow pipeline. '
    + 'Steps: 1=需求分析, 2=写实施计划, 3=逐任务执行, 4=集成验证, 5=代码优化, 6=总结沉淀. '
    + 'Pass step=-1 when the entire workflow is finished.',
    {
      step: z.number().int().min(-1).max(6).describe('Step number (1-6), or -1 to signal workflow completion'),
      session_id: z.string().describe('Your session ID (shown in Sman context header)'),
      task_summary: z.string().optional().describe('Required when step=1: brief description of the user\'s task'),
    },
    async (args: any) => {
      const { step, session_id, task_summary } = args;

      if (step === -1) {
        options.onWorkflowReset?.(session_id);
        return textResult('dev-workflow 流程已结束，进度已重置。');
      }

      if (step === 1 && task_summary) {
        options.onWorkflowActivate?.(session_id, task_summary);
      }
      options.onWorkflowUpdate?.(session_id, step);

      const stepName = STEP_NAMES[step - 1] ?? '未知';
      return textResult(`进度已更新: Step ${step} - ${stepName}`);
    },
  );

  const llmCallTool = tool(
    'llm_call',
    'Send a prompt directly to the LLM and return the response. '
    + 'Use this when you need a one-shot LLM call with a clean prompt — '
    + 'no Claude Code system prompt, no tool definitions, just the user\'s prompt and the raw model response. '
    + 'Ideal for text generation, summarization, translation, analysis, or any task where you want '
    + 'a fresh model response without the current session\'s context influencing the output.',
    {
      prompt: z.string().describe('The complete prompt to send to the LLM. This is sent as-is with no system prompt or tool definitions.'),
    },
    async (args: any) => {
      const { prompt: userPrompt } = args;

      const llmConfig = options.getLlmConfig?.();
      if (!llmConfig) {
        return errorResult('LLM 配置不可用，无法调用。请检查设置中的 API Key 和 Model 配置。');
      }

      try {
        const baseUrl = llmConfig.baseUrl?.replace(/\/$/, '') ?? 'https://api.anthropic.com';
        const response = await fetch(`${baseUrl}/v1/messages`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'x-api-key': llmConfig.apiKey,
            'anthropic-version': '2023-06-01',
            'anthropic-dangerous-direct-browser-access': 'true',
          },
          body: JSON.stringify({
            model: llmConfig.model,
            max_tokens: 16384,
            messages: [
              { role: 'user', content: userPrompt },
            ],
          }),
        });

        if (!response.ok) {
          const errText = await response.text().catch(() => '');
          return errorResult(`LLM 调用失败 (HTTP ${response.status}): ${errText.slice(0, 500)}`);
        }

        const data = await response.json() as any;
        const text = data.content?.[0]?.text ?? '';
        const usage = data.usage;

        const usageNote = usage
          ? `\n\n---\nTokens: ${usage.input_tokens ?? '?'} input, ${usage.output_tokens ?? '?'} output`
          : '';

        return textResult(text + usageNote);
      } catch (e: any) {
        return errorResult(`LLM 调用异常: ${e.message}`);
      }
    },
  );

  return createSdkMcpServer({
    name: 'capability-gateway',
    version: '1.0.0',
    tools: [listTool, loadTool, runTool, workflowUpdateTool, llmCallTool],
  });
}

/** Clean up loaded servers for a session (call on session close) */
export function cleanupLoadedCapabilities(sessionId: string): void {
  loadedServers.delete(sessionId);
}

// --- Runner loading helpers ---

function createRunnerMcpTools(cap: CapabilityEntry, pluginsDir: string): any[] {
  switch (cap.id) {
    case 'office-skills':
      return createOfficeSkillsMcpTools(pluginsDir);
    default:
      throw new Error(`No MCP tool factory for capability: ${cap.id}`);
  }
}

function createRunnerInstructions(cap: CapabilityEntry, pluginsDir: string): string | null {
  switch (cap.id) {
    case 'frontend-slides':
      return createFrontendSlidesInstructions(pluginsDir);
    case 'internal-comms':
    case 'theme-factory':
    case 'skill-creator':
      return createGenericInstructions(pluginsDir, cap.pluginPath);
    default:
      // Generic: try to read SKILL.md from plugin path
      return readPluginSkillMd(cap, pluginsDir);
  }
}

function readPluginSkillMd(cap: CapabilityEntry, pluginsDir: string): string | null {
  const fs = require('node:fs');
  const path = require('node:path');

  // Try common skill file locations
  const candidates = [
    path.join(pluginsDir, cap.pluginPath, 'SKILL.md'),
    path.join(pluginsDir, cap.pluginPath, 'skills', cap.id, 'SKILL.md'),
    path.join(pluginsDir, cap.pluginPath, 'skills', cap.id, 'skill.md'),
  ];

  for (const candidate of candidates) {
    if (fs.existsSync(candidate)) {
      return fs.readFileSync(candidate, 'utf-8');
    }
  }
  return null;
}

function loadRunner(cap: CapabilityEntry, pluginsDir: string): CapabilityRunner {
  // For CLI-subprocess mode, return a simple runner
  return {
    execute: async (command: string, args: Record<string, string>) => {
      return `CLI execution not yet implemented for ${cap.id}: ${command}`;
    },
  };
}
