/**
 * Mandu MCP Server v2
 *
 * DNA 기능 통합:
 * - DNA-001: 플러그인 기반 도구 등록
 * - DNA-006: 설정 핫 리로드
 * - DNA-007: 에러 추출 및 분류
 * - DNA-008: 구조화된 로깅
 * - DNA-016: Pre/Post 도구 훅
 */

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
  ListPromptsRequestSchema,
  GetPromptRequestSchema,
  type CallToolResult,
} from "@modelcontextprotocol/sdk/types.js";

import { loadManduConfig, type ManduConfig } from "@mandujs/core";
import { startWatcher } from "@mandujs/core/watcher";

// DNA-001: 플러그인 기반 도구 레지스트리
import { mcpToolRegistry } from "./registry/mcp-tool-registry.js";
import { registerBuiltinTools, getToolsSummary } from "./tools/index.js";

// DNA-007: 에러 처리

import type { ToolExecutor} from "./executor/tool-executor.js";
import { createToolExecutor } from "./executor/tool-executor.js";

// DNA-008: 로깅 통합
import { setupMcpLogging, teardownMcpLogging } from "./logging/mcp-transport.js";

// DNA-016: 훅 시스템
import { mcpHookRegistry, registerDefaultMcpHooks } from "./hooks/mcp-hooks.js";

// DNA-006: 설정 핫 리로드
import { startMcpConfigWatcher, type McpConfigWatcher } from "./hooks/config-watcher.js";

// Prompts
import { manduPrompts, getPromptResult } from "./prompts.js";

// New top-level resources
import { manduResourceDefinitions, manduResourceHandlers } from "./new-resources.js";

// 기존 컴포넌트
import { resourceHandlers, resourceDefinitions } from "./resources/handlers.js";
import { findProjectRoot } from "./utils/project.js";

import { ActivityMonitor } from "./activity-monitor.js";
import { type McpProfile, resolveMcpProfile } from "./profiles.js";

/**
 * MCP 서버 버전
 */
const MCP_VERSION = "0.12.0";

/**
 * Phase 17 — heap heartbeat interval (ms). Every tick we log
 * `Bun.memoryUsage()` + RSS / heapUsed / external + uptime so operators
 * tailing stderr can spot runaway growth without hooking a debugger.
 *
 * 5 minutes strikes a balance between "noisy enough to catch slow leaks"
 * and "not spammy enough to drown the log" (288 lines/day). Override
 * with `MANDU_MCP_HEAP_INTERVAL_MS` when running under stress tests.
 */
const DEFAULT_HEAP_LOG_INTERVAL_MS = 5 * 60 * 1000;

/**
 * Emit a single heap-usage line to stderr. Called once on startup and
 * again on every heartbeat tick.
 *
 * Format is plain KV — easy to grep / pipe through `awk`:
 *
 *   [MCP heap] rss=142MB heapUsed=56MB heapTotal=80MB external=3MB uptime=310s
 *
 * Uses `Bun.memoryUsage()` when available (returns RSS in bytes + JSC
 * gauges); falls back to `process.memoryUsage()` on Node test runners.
 * Errors are swallowed — a memory probe must never take down the server.
 */
export function logMcpHeapUsage(label: string = "heap"): void {
  try {
    const mem = process.memoryUsage();
    const bunGlobal = (globalThis as { Bun?: { memoryUsage?: () => Record<string, number> } }).Bun;
    let rss = mem.rss;
    let external = mem.external;
    if (bunGlobal?.memoryUsage) {
      try {
        const bunMem = bunGlobal.memoryUsage();
        if (typeof bunMem.rss === "number") rss = bunMem.rss;
        if (typeof bunMem.external === "number") external = bunMem.external;
      } catch {
        // fall back to process.memoryUsage values
      }
    }
    const mb = (n: number) => `${Math.round(n / 1024 / 1024)}MB`;
    const uptime = Math.round(process.uptime());
    console.error(
      `[MCP ${label}] rss=${mb(rss)} heapUsed=${mb(mem.heapUsed)} heapTotal=${mb(mem.heapTotal)} external=${mb(external)} uptime=${uptime}s`,
    );
  } catch {
    // best-effort — never propagate
  }
}

/**
 * ManduMcpServer v2
 *
 * DNA 기능들을 통합한 MCP 서버
 */
export class ManduMcpServer {
  private server: Server;
  private projectRoot: string;
  private monitor: ActivityMonitor;
  private config?: ManduConfig;
  private configWatcher?: McpConfigWatcher;
  private toolExecutor: ToolExecutor;
  private profile: McpProfile;
  /**
   * Phase 17 — 5-minute heap-usage heartbeat. Lazily started in `run()`
   * and cleared by `stop()`. Disabled with `MANDU_MCP_HEAP_INTERVAL_MS=0`.
   */
  private heapLogTimer: ReturnType<typeof setInterval> | null = null;

  constructor(projectRoot: string) {
    this.projectRoot = projectRoot;
    this.monitor = new ActivityMonitor(projectRoot);

    // Resolve profile from environment variable (default: "agent-core")
    this.profile = resolveMcpProfile(process.env.MANDU_MCP_PROFILE);

    // MCP Server 초기화
    this.server = new Server(
      {
        name: "mandu-mcp",
        version: MCP_VERSION,
      },
      {
        capabilities: {
          tools: {},
          resources: {},
          prompts: {},
          logging: {},
        },
      }
    );

    // DNA-001: 플러그인 기반 도구 등록
    registerBuiltinTools(projectRoot, this.server, this.monitor, {
      profile: this.profile,
    });

    // DNA-008: 로깅 통합
    setupMcpLogging({ consoleOutput: false });

    // DNA-016: 기본 훅 등록
    registerDefaultMcpHooks();

    // Tool Executor 생성
    this.toolExecutor = createToolExecutor({
      projectRoot,
      logTool: (name, args, result, error) => this.monitor.logTool(name, args, result, error),
      logResult: (name, result) => this.monitor.logResult(name, result),
    });

    // 핸들러 등록
    this.registerToolHandlers();
    this.registerResourceHandlers();
    this.registerPromptHandlers();
  }

  /**
   * 도구 핸들러 등록 (DNA-001 레지스트리 사용)
   */
  private registerToolHandlers(): void {
    // 도구 목록 요청
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: mcpToolRegistry.toToolDefinitions(),
      };
    });

    // 도구 실행 요청
    this.server.setRequestHandler(CallToolRequestSchema, async (request, _extra) => {
      const { name, arguments: args } = request.params;

      // DNA-007 + DNA-016: Tool Executor로 실행
      const result = await this.toolExecutor.execute(name, args || {});

      return result.response as unknown as CallToolResult;
    });
  }

  /**
   * Prompt handler registration
   */
  private registerPromptHandlers(): void {
    this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
      return { prompts: manduPrompts };
    });

    this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;
      const result = getPromptResult(name, args ?? {});

      if (!result) {
        return {
          messages: [
            {
              role: "user" as const,
              content: {
                type: "text" as const,
                text: `Unknown prompt: ${name}. Available prompts: ${manduPrompts.map((p) => p.name).join(", ")}`,
              },
            },
          ],
        };
      }

      return result;
    });
  }

  /**
   * 리소스 핸들러 등록 (기존 유지)
   */
  private registerResourceHandlers(): void {
    const handlers = resourceHandlers(this.projectRoot);
    const newHandlers = manduResourceHandlers(this.projectRoot);

    // Merge resource definitions (new top-level + existing)
    const allResources = [...manduResourceDefinitions, ...resourceDefinitions];

    this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
      return { resources: allResources };
    });

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

      // Check new top-level resource handlers first
      const newHandler = newHandlers[uri];
      if (newHandler) {
        try {
          const result = await newHandler();
          return { contents: [result] };
        } catch (error) {
          return {
            contents: [
              {
                uri,
                mimeType: "application/json",
                text: JSON.stringify({
                  error: error instanceof Error ? error.message : String(error),
                }),
              },
            ],
          };
        }
      }

      // Fall through to existing resource handlers
      const handler = handlers[uri];
      if (!handler) {
        // 동적 리소스 패턴 매칭
        for (const [pattern, h] of Object.entries(handlers)) {
          if (pattern.includes("{") && matchResourcePattern(pattern, uri)) {
            const params = extractResourceParams(pattern, uri);
            const result = await h(params);
            return {
              contents: [
                {
                  uri,
                  mimeType: "application/json",
                  text: JSON.stringify(result, null, 2),
                },
              ],
            };
          }
        }

        return {
          contents: [
            {
              uri,
              mimeType: "application/json",
              text: JSON.stringify({ error: `Unknown resource: ${uri}` }),
            },
          ],
        };
      }

      try {
        const result = await handler({});
        return {
          contents: [
            {
              uri,
              mimeType: "application/json",
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (error) {
        return {
          contents: [
            {
              uri,
              mimeType: "application/json",
              text: JSON.stringify({
                error: error instanceof Error ? error.message : String(error),
              }),
            },
          ],
        };
      }
    });
  }

  /**
   * 서버 실행
   */
  async run(): Promise<void> {
    // 설정 로드
    try {
      this.config = await loadManduConfig(this.projectRoot);
      this.toolExecutor.updateConfig(this.config);
    } catch {
      // 설정 로드 실패 시 기본값 사용
      console.error("[MCP] Config load failed, using defaults");
    }

    // DNA-006: 설정 핫 리로드 시작
    try {
      this.configWatcher = await startMcpConfigWatcher(this.projectRoot, {
        server: this.server,
        onReload: (newConfig) => {
          this.config = newConfig;
          this.toolExecutor.updateConfig(newConfig);
        },
        onMcpConfigChange: async () => {
          // MCP 설정 변경 시 도구 재등록 가능
          // 현재는 알림만 전송
        },
      });
    } catch {
      console.error("[MCP] Config watcher start failed (non-critical)");
    }

    // 서버 연결
    const transport = new StdioServerTransport();
    await this.server.connect(transport);

    // 모니터 시작
    this.monitor.start();

    // 와처 자동 시작
    try {
      const watcher = await startWatcher({ rootDir: this.projectRoot });
      watcher.onWarning((warning) => {
        this.monitor.logWatch(
          warning.level || "warn",
          warning.ruleId,
          warning.file,
          warning.message
        );

        // MCP 클라이언트에 알림
        this.server.sendLoggingMessage({
          level: "warning",
          logger: "mandu-watch",
          data: {
            type: "watch_warning",
            severity: warning.level || "warn",
            ruleId: warning.ruleId,
            file: warning.file,
            message: warning.message,
            event: warning.event,
            agentAction: warning.agentAction || null,
            agentCommand: warning.agentCommand || null,
          },
        }).catch(() => {});
      });

      this.monitor.logEvent("SYSTEM", "Watcher auto-started");
    } catch {
      this.monitor.logEvent("SYSTEM", "Watcher auto-start failed (non-critical)");
    }

    // 시작 로그
    const summary = getToolsSummary();
    console.error(`Mandu MCP Server v${MCP_VERSION} running`);
    console.error(`  Project: ${this.projectRoot}`);
    console.error(`  Profile: ${this.profile}`);
    console.error(`  Tools: ${summary.total} (${summary.categories.join(", ")})`);

    // Phase 17 — start the heap heartbeat. Startup line is tagged
    // `startup` so operators can anchor before/after diffs to it.
    logMcpHeapUsage("startup");
    this.startHeapHeartbeat();
  }

  /**
   * Phase 17 — configure the heap-usage heartbeat. Reads
   * `MANDU_MCP_HEAP_INTERVAL_MS` for override (`0` = disabled).
   * Idempotent: repeat calls replace the previous timer.
   */
  private startHeapHeartbeat(): void {
    if (this.heapLogTimer) {
      clearInterval(this.heapLogTimer);
      this.heapLogTimer = null;
    }
    const raw = process.env.MANDU_MCP_HEAP_INTERVAL_MS;
    let intervalMs = DEFAULT_HEAP_LOG_INTERVAL_MS;
    if (raw !== undefined) {
      const parsed = Number(raw);
      if (Number.isFinite(parsed) && parsed >= 0) intervalMs = parsed;
    }
    if (intervalMs === 0) return; // explicitly disabled
    this.heapLogTimer = setInterval(() => logMcpHeapUsage("heartbeat"), intervalMs);
    // `unref()` so the heartbeat never keeps the event loop alive on its
    // own — `stop()` must actively clear it, but a forgotten stop should
    // not hang the process.
    if (typeof this.heapLogTimer.unref === "function") {
      this.heapLogTimer.unref();
    }
  }

  /**
   * 서버 종료
   */
  async stop(): Promise<void> {
    // 설정 감시 중지
    this.configWatcher?.stop();

    // Phase 17 — stop the heap heartbeat. Safe to call when never started.
    if (this.heapLogTimer) {
      clearInterval(this.heapLogTimer);
      this.heapLogTimer = null;
    }

    // 로깅 해제
    teardownMcpLogging();

    // 모니터 종료
    this.monitor.stop();

    // 훅 정리
    mcpHookRegistry.clear();

    // 도구 레지스트리 정리
    mcpToolRegistry.clear();
  }

  /**
   * 현재 설정 반환
   */
  getConfig(): ManduConfig | undefined {
    return this.config;
  }

  /**
   * 도구 레지스트리 접근
   */
  getToolRegistry(): typeof mcpToolRegistry {
    return mcpToolRegistry;
  }

  /**
   * 훅 레지스트리 접근
   */
  getHookRegistry(): typeof mcpHookRegistry {
    return mcpHookRegistry;
  }
}

// ============================================
// 유틸리티 함수
// ============================================

/**
 * 리소스 패턴 매칭
 */
function matchResourcePattern(pattern: string, uri: string): boolean {
  const regexPattern = pattern
    .split(/\{[^}]+\}/)
    .map(part => part.replace(/[.+*?^${}()|[\]\\]/g, "\\$&"))
    .join("([^/]+)");
  const regex = new RegExp(`^${regexPattern}$`);
  return regex.test(uri);
}

/**
 * 리소스 파라미터 추출
 */
function extractResourceParams(pattern: string, uri: string): Record<string, string> {
  const paramNames: string[] = [];
  const regexPattern = pattern
    .split(/\{([^}]+)\}/)
    .map((part, index) => {
      if (index % 2 === 1) {
        paramNames.push(part);
        return "([^/]+)";
      }
      return part.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
    })
    .join("");

  const regex = new RegExp(`^${regexPattern}$`);
  const match = uri.match(regex);

  if (!match) return {};

  const params: Record<string, string> = {};
  paramNames.forEach((name, index) => {
    params[name] = match[index + 1];
  });

  return params;
}

/**
 * MCP 서버 시작
 */
export async function startServer(projectRoot?: string): Promise<void> {
  const root = projectRoot || (await findProjectRoot()) || process.cwd();
  const server = new ManduMcpServer(root);
  await server.run();
}

// Re-exports
export { mcpToolRegistry } from "./registry/mcp-tool-registry.js";
export { mcpHookRegistry } from "./hooks/mcp-hooks.js";
export { registerBuiltinTools } from "./tools/index.js";
