/**
 * stdio 传输实现
 * 通过子进程的 stdin/stdout 与 MCP 服务器通信
 */

import { spawn, type ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import type {
  Transport,
  JSONRPCRequest,
  JSONRPCResponse,
  JSONRPCNotification,
  LocalMCPServer,
} from '../types.js';

/** 默认请求超时 */
const DEFAULT_TIMEOUT = 30000;

/** 待处理请求 */
interface PendingRequest {
  resolve: (result: unknown) => void;
  reject: (error: Error) => void;
  timer: NodeJS.Timeout;
}

/**
 * stdio 传输层实现
 */
export class StdioTransport extends EventEmitter implements Transport {
  private process: ChildProcess | null = null;
  private nextId = 1;
  private pendingRequests = new Map<string | number, PendingRequest>();
  private buffer = '';
  private notificationHandler?: (method: string, params: unknown) => void;
  private closeHandler?: (error?: Error) => void;
  private isClosing = false;

  constructor(
    private config: LocalMCPServer,
    private serverName: string
  ) {
    super();
  }

  /**
   * 启动传输（创建子进程）
   */
  async start(): Promise<void> {
    if (this.process) {
      throw new Error('Transport already started');
    }

    const [command, ...args] = this.config.command;

    return new Promise((resolve, reject) => {
      try {
        this.process = spawn(command, args, {
          cwd: this.config.cwd,
          env: {
            ...process.env,
            ...this.config.env,
          },
          stdio: ['pipe', 'pipe', 'pipe'],
        });

        // 处理 stdout（JSON-RPC 消息）
        this.process.stdout?.on('data', (data: Buffer) => {
          this.handleData(data);
        });

        // 处理 stderr（日志输出）
        this.process.stderr?.on('data', (data: Buffer) => {
          const message = data.toString().trim();
          if (message) {
            console.debug(`[MCP:${this.serverName}] ${message}`);
          }
        });

        // 处理进程错误
        this.process.on('error', (error) => {
          if (!this.isClosing) {
            this.handleClose(error);
          }
          reject(error);
        });

        // 处理进程退出
        this.process.on('exit', (code, signal) => {
          if (!this.isClosing) {
            const error =
              code !== 0
                ? new Error(
                    `Process exited with code ${code}${signal ? ` (signal: ${signal})` : ''}`
                  )
                : undefined;
            this.handleClose(error);
          }
        });

        // 给进程一点时间启动
        setTimeout(() => {
          if (this.process && !this.process.killed) {
            resolve();
          }
        }, 100);
      } catch (error) {
        reject(error);
      }
    });
  }

  /**
   * 关闭传输
   */
  async close(): Promise<void> {
    this.isClosing = true;

    // 拒绝所有待处理请求
    for (const [id, pending] of this.pendingRequests) {
      clearTimeout(pending.timer);
      pending.reject(new Error('Transport closed'));
      this.pendingRequests.delete(id);
    }

    // 终止子进程
    if (this.process && !this.process.killed) {
      return new Promise((resolve) => {
        const timeout = setTimeout(() => {
          this.process?.kill('SIGKILL');
          resolve();
        }, 5000);

        this.process!.on('exit', () => {
          clearTimeout(timeout);
          resolve();
        });

        this.process!.kill('SIGTERM');
      });
    }
  }

  /**
   * 发送请求并等待响应
   */
  async request(method: string, params?: unknown): Promise<unknown> {
    if (!this.process || this.process.killed) {
      throw new Error('Transport not connected');
    }

    const id = this.nextId++;
    const request: JSONRPCRequest = {
      jsonrpc: '2.0',
      id,
      method,
      params,
    };

    return new Promise((resolve, reject) => {
      const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;
      const timer = setTimeout(() => {
        this.pendingRequests.delete(id);
        reject(new Error(`Request timeout after ${timeout}ms: ${method}`));
      }, timeout);

      this.pendingRequests.set(id, { resolve, reject, timer });
      this.send(request);
    });
  }

  /**
   * 发送通知（无响应）
   */
  async notify(method: string, params?: unknown): Promise<void> {
    if (!this.process || this.process.killed) {
      throw new Error('Transport not connected');
    }

    const notification: JSONRPCNotification = {
      jsonrpc: '2.0',
      method,
      params,
    };

    this.send(notification);
  }

  /**
   * 监听服务器通知
   */
  onNotification(handler: (method: string, params: unknown) => void): void {
    this.notificationHandler = handler;
  }

  /**
   * 监听连接关闭
   */
  onClose(handler: (error?: Error) => void): void {
    this.closeHandler = handler;
  }

  /**
   * 发送 JSON-RPC 消息
   */
  private send(message: JSONRPCRequest | JSONRPCNotification): void {
    const data = JSON.stringify(message) + '\n';
    this.process?.stdin?.write(data);
  }

  /**
   * 处理接收到的数据
   */
  private handleData(data: Buffer): void {
    this.buffer += data.toString();

    // 按行分割处理
    const lines = this.buffer.split('\n');
    this.buffer = lines.pop() || ''; // 保留不完整的行

    for (const line of lines) {
      if (line.trim()) {
        try {
          this.handleMessage(JSON.parse(line));
        } catch (error) {
          console.error(
            `[MCP:${this.serverName}] Failed to parse message:`,
            line
          );
        }
      }
    }
  }

  /**
   * 处理 JSON-RPC 消息
   */
  private handleMessage(message: JSONRPCResponse | JSONRPCNotification): void {
    // 响应消息
    if ('id' in message && message.id !== undefined) {
      const pending = this.pendingRequests.get(message.id);
      if (pending) {
        clearTimeout(pending.timer);
        this.pendingRequests.delete(message.id);

        if ('error' in message && message.error) {
          pending.reject(
            new Error(
              `${message.error.message} (code: ${message.error.code})`
            )
          );
        } else {
          pending.resolve(message.result);
        }
      }
      return;
    }

    // 通知消息
    if ('method' in message) {
      this.notificationHandler?.(message.method, message.params);
    }
  }

  /**
   * 处理连接关闭
   */
  private handleClose(error?: Error): void {
    // 拒绝所有待处理请求
    for (const [id, pending] of this.pendingRequests) {
      clearTimeout(pending.timer);
      pending.reject(error || new Error('Connection closed'));
      this.pendingRequests.delete(id);
    }

    this.closeHandler?.(error);
    this.process = null;
  }
}
