/**
 * Memory Consolidator — 会话结束记忆固化器
 *
 * 每次 Agent 完成一轮对话后，调用 LLM 判断本次对话中哪些内容
 * 值得持久化到工作空间根目录的 MEMORY.md。
 *
 * 工作流程：
 * 1. 收集本次对话的 user message + assistant final answer + tool calls 摘要
 * 2. 读取当前 MEMORY.md 内容作为参考（避免重复）
 * 3. 调用 LLM，要求其返回 JSON 数组 [{section, entry}]
 * 4. 解析响应，调用 WorkspaceService.appendMemory 追加
 *
 * 安全约束：
 * - 仅追加新条目，不修改已有内容
 * - section 限定为预定义的 5 个分区
 * - 单次最多追加 5 条记忆，避免 LLM 过度提取
 * - LLM 调用失败时静默降级（不影响主流程）
 *
 * @see docs/生产级通用 AI Agent 智能体桌面应用：完整设计与构建指南.html — 第六章
 */

import { nanoid } from 'nanoid';
import log from 'electron-log';
import type { IMetonaProviderAdapter } from '../types/metona-adapter';
import type { MetonaRequest, MetonaMessage } from '../types';
import type { WorkspaceService } from '../../services/workspace.service';
import type { IterationStep } from '../agent-loop/types';
import type { MemoryManager } from './manager';

/** 允许写入的 MEMORY.md 分区（与 WorkspaceService.MEMORY_TEMPLATE 对齐） */
const ALLOWED_SECTIONS = ['用户偏好', '项目上下文', '重要决策', '待办事项', '已知问题'] as const;
type AllowedSection = typeof ALLOWED_SECTIONS[number];

/** 单次固化最多追加的条目数 */
const MAX_ENTRIES_PER_CONSOLIDATION = 5;

/** 单条记忆最大长度 */
const MAX_ENTRY_LENGTH = 500;

export interface ConsolidationResult {
  appended: number;
  entries: Array<{ section: string; entry: string }>;
  skipped: number;
}

export class MemoryConsolidator {
  /** v0.3.18 修复: 跟踪进行中的 consolidate 任务，供应用退出时等待 */
  private runningPromise: Promise<ConsolidationResult> | null = null;
  /** v0.3.18 修复: 注入 MemoryManager，实现 DB 记忆与 MEMORY.md 双轨交叉写入 */
  private memoryManager: MemoryManager | null = null;

  constructor(
    private adapter: IMetonaProviderAdapter,
    private workspaceService: WorkspaceService,
  ) {}

  /**
   * 热切换 Adapter（配置变更时由 main.ts 调用）
   */
  setAdapter(adapter: IMetonaProviderAdapter): void {
    this.adapter = adapter;
  }

  /**
   * v0.3.18 修复: 注入 MemoryManager，使 consolidate 同步写入 semantic_memories 表
   */
  setMemoryManager(memoryManager: MemoryManager): void {
    this.memoryManager = memoryManager;
  }

  /**
   * v0.3.18 修复: 是否有进行中的 consolidate 任务
   */
  isRunning(): boolean {
    return this.runningPromise !== null;
  }

  /**
   * v0.3.18 修复: 等待进行中的 consolidate 任务完成（应用退出时调用）
   *
   * @param timeoutMs 超时时间（默认 35 秒，略大于 LLM 调用的 30 秒超时）
   * @returns true 表示已完成，false 表示超时
   */
  async waitForCompletion(timeoutMs: number = 35_000): Promise<boolean> {
    if (!this.runningPromise) return true;
    let timedOut = false;
    let timerHandle: ReturnType<typeof setTimeout> | undefined;
    const timer = new Promise<void>((resolve) => {
      timerHandle = setTimeout(() => {
        timedOut = true;
        resolve();
      }, timeoutMs);
    });
    try {
      await Promise.race([
        this.runningPromise.catch(() => {}),
        timer,
      ]);
      return !timedOut;
    } finally {
      if (timerHandle) clearTimeout(timerHandle);
    }
  }

  /**
   * 固化本次对话的重要记忆到 MEMORY.md
   *
   * @param userMessage 用户原始消息
   * @param assistantAnswer Agent 最终回答
   * @param iterations Agent Loop 迭代步骤（用于提取工具调用摘要）
   * @returns 固化结果
   */
  async consolidate(
    userMessage: string,
    assistantAnswer: string,
    iterations: IterationStep[],
  ): Promise<ConsolidationResult> {
    // v0.3.18 修复: 跟踪进行中的 consolidate，供应用退出时等待
    const promise = this.executeConsolidation(userMessage, assistantAnswer, iterations);
    this.runningPromise = promise;
    try {
      return await promise;
    } finally {
      // 只有当前 promise 仍是自己时才清理，避免被后续调用覆盖
      if (this.runningPromise === promise) {
        this.runningPromise = null;
      }
    }
  }

  /**
   * 实际执行固化的内部方法
   *
   * v0.3.18 修复: 同步写入 semantic_memories 表，实现 DB 记忆与 MEMORY.md 双轨交叉
   * 之前仅写 MEMORY.md，导致 DB 检索不到用户偏好等非工具触发的重要信息。
   * 现在每条固化到 MEMORY.md 的条目也同步存入 semantic_memories 表，
   * 使 TF-IDF 检索能命中跨会话的用户偏好/决策/项目上下文。
   */
  private async executeConsolidation(
    userMessage: string,
    assistantAnswer: string,
    iterations: IterationStep[],
  ): Promise<ConsolidationResult> {
    try {
      // 1. 构建对话摘要
      const conversationDigest = this.buildConversationDigest(userMessage, assistantAnswer, iterations);
      if (!conversationDigest) {
        return { appended: 0, entries: [], skipped: 0 };
      }

      // 2. 读取当前 MEMORY.md 内容（供 LLM 去重）
      const currentMemory = this.workspaceService.getFiles().memory;
      const memoryDigest = this.truncateMemoryForPrompt(currentMemory);

      // 3. 调用 LLM 提取需要持久化的记忆
      const llmResponse = await this.callLLMForExtraction(conversationDigest, memoryDigest);
      if (!llmResponse) {
        return { appended: 0, entries: [], skipped: 0 };
      }

      // 4. 解析 JSON 响应
      const extracted = this.parseExtractionResponse(llmResponse);
      if (extracted.length === 0) {
        log.debug('[MemoryConsolidator] No memories worth persisting');
        return { appended: 0, entries: [], skipped: 0 };
      }

      // 5. 过滤 + 截断 + 追加到 MEMORY.md
      const validEntries: Array<{ section: string; entry: string }> = [];
      let skipped = 0;

      for (const item of extracted.slice(0, MAX_ENTRIES_PER_CONSOLIDATION)) {
        if (!this.isValidEntry(item)) {
          skipped++;
          continue;
        }
        const truncatedEntry = item.entry.slice(0, MAX_ENTRY_LENGTH);
        try {
          this.workspaceService.appendMemory(item.section, truncatedEntry);
          validEntries.push({ section: item.section, entry: truncatedEntry });

          // v0.3.18 修复: 同步写入 semantic_memories 表，实现 DB 记忆与 MEMORY.md 双轨交叉
          // 之前仅写 MEMORY.md，导致 DB 检索不到用户偏好等非工具触发的重要信息。
          // 现在每条固化到 MEMORY.md 的条目也存入 semantic_memories，
          // key 用 section（如"用户偏好"），value 用 entry 内容，
          // confidence 按 section 语义分级（偏好/决策最高，待办最低）
          if (this.memoryManager) {
            try {
              const confidence = this.getSectionConfidence(item.section);
              this.memoryManager.store({
                type: 'semantic',
                content: truncatedEntry,
                summary: `[${item.section}] ${truncatedEntry.slice(0, 60)}`,
                source: 'agent_thought',
                importance: confidence,
              });
            } catch (dbErr) {
              // DB 写入失败不影响 MEMORY.md 已写入的结果，仅记录日志
              log.warn(`[MemoryConsolidator] Failed to sync to semantic_memories:`, dbErr);
            }
          }
        } catch (err) {
          log.warn(`[MemoryConsolidator] Failed to append to section "${item.section}":`, err);
          skipped++;
        }
      }

      if (validEntries.length > 0) {
        log.info(`[MemoryConsolidator] Persisted ${validEntries.length} memories to MEMORY.md (skipped: ${skipped})`);
      }

      return { appended: validEntries.length, entries: validEntries, skipped };
    } catch (error) {
      log.error('[MemoryConsolidator] Consolidation failed:', error);
      return { appended: 0, entries: [], skipped: 0 };
    }
  }

  /**
   * 构建对话摘要（供 LLM 分析）
   */
  private buildConversationDigest(
    userMessage: string,
    assistantAnswer: string,
    iterations: IterationStep[],
  ): string {
    const parts: string[] = [];

    // 用户消息
    parts.push(`[USER] ${this.truncate(userMessage, 2000)}`);

    // 工具调用摘要（如果有）
    const toolSummaries: string[] = [];
    for (const iter of iterations) {
      if (!iter.toolCalls?.length) continue;
      for (let i = 0; i < iter.toolCalls.length; i++) {
        const tc = iter.toolCalls[i];
        const result = iter.toolResults?.find((r) => r.toolCallId === tc.id);
        const status = result?.success ? 'ok' : 'error';
        const resultPreview = result?.result
          ? this.truncate(JSON.stringify(result.result), 200)
          : result?.error ?? '';
        toolSummaries.push(`  - ${tc.name}(${this.truncate(JSON.stringify(tc.args), 100)}) [${status}]${resultPreview ? ': ' + resultPreview : ''}`);
      }
    }
    if (toolSummaries.length > 0) {
      parts.push(`[TOOLS]\n${toolSummaries.join('\n')}`);
    }

    // Agent 最终回答
    parts.push(`[ASSISTANT] ${this.truncate(assistantAnswer, 2000)}`);

    return parts.join('\n\n');
  }

  /**
   * 截断 MEMORY.md 内容用于 prompt（避免过长）
   */
  private truncateMemoryForPrompt(memory: string): string {
    if (!memory) return '(empty)';
    // 截取前 3000 字符，保留分区结构概览
    if (memory.length <= 3000) return memory;
    return memory.slice(0, 3000) + '\n... (truncated)';
  }

  /**
   * 调用 LLM 提取需要持久化的记忆
   */
  private async callLLMForExtraction(
    conversationDigest: string,
    currentMemory: string,
  ): Promise<string | null> {
    const sectionsList = ALLOWED_SECTIONS.map((s) => `"${s}"`).join(', ');

    const request: MetonaRequest = {
      meta: {
        sessionId: 'memory-consolidation',
        iteration: 0,
        requestId: `mc_${nanoid(12)}`,
        timestamp: Date.now(),
        agentVersion: '1.0.0',
      },
      systemPrompt: {
        roleDefinition: 'You are a memory curator for an AI agent. Your job is to decide what information from the current conversation is worth persisting to the agent\'s long-term memory file (MEMORY.md) for future sessions.',
        outputConstraints: [
          'Analyze the conversation below and extract ONLY information that meets ALL of these criteria:',
          '1. Long-term value: will be useful in future conversations (not transient task state)',
          '2. Not already present in the current MEMORY.md (avoid duplicates)',
          '3. Concrete and actionable (not vague observations)',
          '',
          'Good candidates: user preferences, project facts, important decisions, pending todos, known issues.',
          'Bad candidates: temporary tool results, trivial conversation, already-known facts.',
          '',
          `Output a JSON array. Each element: {"section": one of ${sectionsList}, "entry": concise description (max 200 chars, in the conversation language)}`,
          'If nothing is worth persisting, output an empty array: []',
          'Output ONLY the JSON array, no markdown fences, no explanation.',
        ].join('\n'),
        safetyGuidelines: 'Do not persist sensitive data (passwords, API keys, tokens). Do not persist user personal information beyond what is necessary for the agent to function.',
      },
      messages: [{
        role: 'user',
        content: `## Current MEMORY.md content:\n\n${currentMemory}\n\n## Current conversation:\n\n${conversationDigest}\n\n## Task:\nExtract information worth persisting. Output JSON array only.`,
        timestamp: Date.now(),
      }],
      params: {
        maxTokens: 1024,
        temperature: 0.0,
        stream: false,
        thinkingEnabled: false,
        thinkingEffort: 'low',
      },
    };

    try {
      // v0.3.0 修复: 添加 30 秒超时保护，防止 LLM 响应缓慢导致 consolidator 任务挂起
      // M-17 修复: 使用 try/finally 清理 setTimeout，防止每次会话结束时 timer 堆积
      let timer: ReturnType<typeof setTimeout> | undefined;
      try {
        const timeoutPromise = new Promise<never>((_, reject) => {
          timer = setTimeout(() => reject(new Error('LLM extraction timeout')), 30_000);
        });
        const response = await Promise.race([this.adapter.send(request), timeoutPromise]);
        return response.content.trim();
      } finally {
        // M-17 修复: LLM 调用正常完成时清理未触发的 30 秒 timer
        if (timer) clearTimeout(timer);
      }
    } catch (error) {
      log.warn('[MemoryConsolidator] LLM call failed:', (error as Error).message);
      return null;
    }
  }

  /**
   * 解析 LLM 的提取响应
   */
  private parseExtractionResponse(raw: string): Array<{ section: string; entry: string }> {
    if (!raw) return [];

    // 尝试直接解析 JSON
    let cleaned = raw.trim();

    // 移除可能的 markdown 代码围栏
    if (cleaned.startsWith('```')) {
      cleaned = cleaned.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '').trim();
    }

    try {
      const parsed = JSON.parse(cleaned);
      if (!Array.isArray(parsed)) return [];

      return parsed
        .filter((item): item is { section: string; entry: string } =>
          typeof item === 'object' && item !== null &&
          typeof item.section === 'string' && typeof item.entry === 'string',
        )
        .map((item) => ({
          section: item.section.trim(),
          entry: item.entry.trim(),
        }))
        .filter((item) => item.section && item.entry);
    } catch {
      log.warn('[MemoryConsolidator] Failed to parse LLM response as JSON:', cleaned.slice(0, 200));
      return [];
    }
  }

  /**
   * 校验条目是否合法（section 在允许列表内）
   */
  private isValidEntry(item: { section: string; entry: string }): boolean {
    return (ALLOWED_SECTIONS as readonly string[]).includes(item.section) && item.entry.length > 0;
  }

  /**
   * v0.3.18 修复: 按 MEMORY.md 分区语义返回 confidence（重要度）
   *
   * 用于同步写入 semantic_memories 时的 importance 字段：
   * - 用户偏好/重要决策：0.9（长期有效，跨会话高价值）
   * - 项目上下文/已知问题：0.7（项目级，中等价值）
   * - 待办事项：0.5（短期，可能很快完成）
   */
  private getSectionConfidence(section: string): number {
    switch (section) {
      case '用户偏好':
      case '重要决策':
        return 0.9;
      case '项目上下文':
      case '已知问题':
        return 0.7;
      case '待办事项':
        return 0.5;
      default:
        return 0.6;
    }
  }

  /**
   * 截断字符串
   */
  private truncate(text: string, maxLen: number): string {
    if (text.length <= maxLen) return text;
    return text.slice(0, maxLen) + '...';
  }
}
