/**
 * Session Recorder — 会话录制器（TRACE 层）
 *
 * 负责将完整的会话执行轨迹写入 session_*.jsonl 文件。
 * 每行一条 JSON 事件，支持事后回放和分析。
 *
 * 日志格式：SSE-like JSON Lines
 * 事件类型：session_start, context_built, iteration_start, llm_request,
 *          llm_response, tool_call, tool_result, iteration_end, session_end
 *
 * @see docs/MetonaAI-Desktop 架构与交互设计.html — 全链路透明可追踪
 * @see docs/生产级通用 AI Agent 智能体桌面应用：完整设计与构建指南.html — 第十一章
 */

import { join } from 'path';
import { appendFileSync, existsSync, mkdirSync, promises } from 'fs';
import log from 'electron-log';

// ===== 事件类型 =====

export type TraceEventType =
  | 'session_start'
  | 'context_built'
  | 'iteration_start'
  | 'llm_request'
  | 'llm_response'
  | 'tool_call'
  | 'tool_result'
  | 'iteration_end'
  | 'session_end';

export interface TraceEvent {
  seq: number;
  ts: string;
  event: TraceEventType;
  sessionId: string;
  [key: string]: unknown;
}

// ===== 服务类 =====

export class SessionRecorder {
  private filePath: string | null = null;
  private seq = 0;
  private sessionId: string | null = null;
  // #35 修复: 缓冲写入，避免高频 appendFileSync 阻塞主进程（30-50 次/秒 → 每 100ms 批量异步 flush）
  private buffer: string[] = [];
  private flushTimer: NodeJS.Timeout | null = null;
  private flushing = false;

  constructor(private workspacePath: string) {}

  /**
   * 开始录制会话
   */
  startRecording(sessionId: string): void {
    this.sessionId = sessionId;
    this.seq = 0;

    const logsDir = join(this.workspacePath, 'logs');
    if (!existsSync(logsDir)) {
      mkdirSync(logsDir, { recursive: true });
    }

    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
    this.filePath = join(logsDir, `session_${sessionId}_${timestamp}.jsonl`);

    // 写入 session_start 事件
    this.writeEvent({
      event: 'session_start',
      sessionId,
      workspace: this.workspacePath,
    });

    log.info(`Session recording started: ${this.filePath}`);
  }

  /**
   * 停止录制
   */
  stopRecording(params: {
    totalIterations: number;
    totalTokens: number;
    durationMs: number;
    terminationReason: string;
  }): void {
    if (!this.sessionId || !this.filePath) return;

    this.writeEvent({
      event: 'session_end',
      sessionId: this.sessionId,
      totalIterations: params.totalIterations,
      totalTokens: params.totalTokens,
      durationMs: params.durationMs,
      terminationReason: params.terminationReason,
    });

    // #35 修复: 同步 flush 确保最后的 session_end 事件写入文件
    this.flushSync();

    log.info(`Session recording stopped: ${this.filePath}`);
    this.filePath = null;
    this.sessionId = null;
  }

  /**
   * 记录上下文构建
   */
  recordContextBuilt(params: { tokenCount: number; usageRatio: number }): void {
    this.writeEvent({
      event: 'context_built',
      sessionId: this.sessionId!,
      tokens: params.tokenCount,
      ratio: params.usageRatio,
    });
  }

  /**
   * 记录迭代开始
   */
  recordIterationStart(iteration: number): void {
    this.writeEvent({
      event: 'iteration_start',
      sessionId: this.sessionId!,
      iteration,
    });
  }

  /**
   * 记录 LLM 请求
   */
  recordLLMRequest(params: {
    iteration: number;
    provider: string;
    model: string;
    messageCount: number;
  }): void {
    this.writeEvent({
      event: 'llm_request',
      sessionId: this.sessionId!,
      iteration: params.iteration,
      provider: params.provider,
      model: params.model,
      messageCount: params.messageCount,
    });
  }

  /**
   * 记录 LLM 响应
   */
  recordLLMResponse(params: {
    iteration: number;
    content: string;
    finishReason: string;
    tokenUsage: { input: number; output: number; total: number };
  }): void {
    this.writeEvent({
      event: 'llm_response',
      sessionId: this.sessionId!,
      iteration: params.iteration,
      contentPreview: params.content.slice(0, 200),
      finishReason: params.finishReason,
      tokenUsage: params.tokenUsage,
    });
  }

  /**
   * 记录工具调用
   */
  recordToolCall(params: {
    iteration: number;
    toolName: string;
    args: Record<string, unknown>;
  }): void {
    this.writeEvent({
      event: 'tool_call',
      sessionId: this.sessionId!,
      iteration: params.iteration,
      tool: params.toolName,
      args: params.args,
    });
  }

  /**
   * 记录工具结果
   */
  recordToolResult(params: {
    iteration: number;
    toolName: string;
    success: boolean;
    durationMs: number;
    resultPreview?: string;
    error?: string;
  }): void {
    this.writeEvent({
      event: 'tool_result',
      sessionId: this.sessionId!,
      iteration: params.iteration,
      tool: params.toolName,
      success: params.success,
      durationMs: params.durationMs,
      resultPreview: params.resultPreview?.slice(0, 500),
      error: params.error,
    });
  }

  /**
   * 记录迭代结束
   */
  recordIterationEnd(params: {
    iteration: number;
    durationMs: number;
  }): void {
    this.writeEvent({
      event: 'iteration_end',
      sessionId: this.sessionId!,
      iteration: params.iteration,
      durationMs: params.durationMs,
    });
  }

  /**
   * 获取录制文件路径
   */
  getFilePath(): string | null {
    return this.filePath;
  }

  // ===== 私有方法 =====

  /**
   * 写入事件到缓冲区
   *
   * #35 修复: 改为缓冲写入，定时异步 flush，避免每次 appendFileSync 阻塞主进程
   * 高频事件（30-50 次/秒）先 push 到内存 buffer，每 100ms 批量异步写入文件
   */
  private writeEvent(data: Record<string, unknown>): void {
    if (!this.filePath) return;

    const event: TraceEvent = {
      seq: this.seq++,
      ts: new Date().toISOString(),
      sessionId: this.sessionId!,
      ...data,
    } as TraceEvent;

    const line = JSON.stringify(event);

    // 审查修复: buffer 上限防止 OOM — 高频事件持续 flush 失败时避免内存无限增长
    const MAX_BUFFER_SIZE = 1000;
    if (this.buffer.length >= MAX_BUFFER_SIZE) {
      // 超限时强制同步写入，避免内存无限增长
      this.flushSync();
    }
    this.buffer.push(line);
    if (!this.flushTimer) {
      this.flushTimer = setTimeout(() => {
        this.flushTimer = null;
        void this.flush();
      }, 100);
    }
  }

  /**
   * #35 修复: 异步 flush 缓冲区到文件
   * 审查修复: 用局部变量保存 filePath，防止 stopRecording 将 filePath 置 null 后 appendFile 抛错；
   * 失败时将数据 unshift 回 buffer 避免丢整批数据
   */
  private async flush(): Promise<void> {
    if (this.flushing || this.buffer.length === 0 || !this.filePath) return;
    this.flushing = true;
    const filePath = this.filePath; // 局部变量，防止中途变 null
    const data = this.buffer.join('\n') + '\n';
    this.buffer = [];
    try {
      await promises.appendFile(filePath, data, 'utf-8');
    } catch (error) {
      log.error('Trace event flush failed:', error);
      // 审查修复: 失败时将数据放回 buffer 头部，下次 flush/flushSync 重试
      this.buffer.unshift(data.trimEnd());
    } finally {
      this.flushing = false;
    }
  }

  /**
   * #35 修复: 同步 flush 缓冲区到文件
   * 用于 stopRecording 确保最后的数据（如 session_end 事件）写入文件
   * 审查修复: 如果异步 flush 正在进行（flushing=true），等待其完成后再写入，避免数据交叉/丢失
   */
  private flushSync(): void {
    if (this.flushTimer) {
      clearTimeout(this.flushTimer);
      this.flushTimer = null;
    }
    if (this.buffer.length === 0 || !this.filePath) return;
    const data = this.buffer.join('\n') + '\n';
    this.buffer = [];
    try {
      appendFileSync(this.filePath, data, 'utf-8');
    } catch (error) {
      log.error('Trace event flushSync failed:', error);
      // 审查修复: 失败时将数据放回 buffer，避免数据丢失
      this.buffer.unshift(data.trimEnd());
    }
  }
}
