/**
 * Confirmation Hook — 工具执行前用户确认钩子（v0.2.0 新增）
 *
 * 对 requiresPermission=true 或 riskLevel >= HIGH 的工具，
 * 通过 IPC 请求用户确认后再执行。
 *
 * 支持两种自动执行机制：
 * 1. 持久化自动执行（跨会话）：存储在 ConfigService `tools.{toolName}.autoExecute`
 * 2. 会话内记忆：同一会话内不再重复询问（rememberDecisions）
 *
 * @see docs/生产级通用 AI Agent 智能体桌面应用：完整设计与构建指南.html — 第五章
 */

import { BrowserWindow } from 'electron';
import type { MetonaToolCall, MetonaToolDef } from '../types';
import type { PreToolHook, HookResult } from './pre-tool';
import type { ConfigService } from '../../services/config.service';

export interface ConfirmationRequest {
  toolCallId: string;
  toolName: string;
  args: Record<string, unknown>;
  riskLevel: string;
  reason: string;
  /**
   * 过期时间戳（ms），由 waitForConfirmation 注入，用于前端倒计时 UI。
   * 注意：beforeExecute 构造 request 时不带此字段，仅在 waitForConfirmation 中追加。
   */
  expiresAt?: number;
}

export class ConfirmationHook implements PreToolHook {
  /** 需要确认的风险等级 */
  private static REQUIRES_CONFIRMATION = ['high', 'critical'];

  /** L-12 修复: 确认超时范围魔法数字提取为命名常量（30 秒 ~ 600 秒） */
  private static readonly MIN_CONFIRMATION_TIMEOUT_MS = 30_000;
  private static readonly MAX_CONFIRMATION_TIMEOUT_MS = 600_000;

  /** 工具定义缓存（由外部设置） */
  private toolDefs = new Map<string, MetonaToolDef>();

  /** 用户选择记忆（同一会话内不再重复询问） */
  private rememberedDecisions = new Map<string, boolean>();

  /** 持久化自动执行的工具集合（从 ConfigService 加载，跨会话生效） */
  private autoExecuteTools = new Set<string>();

  /** 等待确认的 Promise 解析器（含完整请求信息，供 getPendingConfirmations 返回） */
  private pendingConfirmations = new Map<string, {
    resolve: (v: boolean) => void;
    timer: NodeJS.Timeout;
    toolName: string;
    expiresAt: number;
    /** v0.3.2 批量审批：缓存完整请求信息，供 getPendingConfirmations() 重建 ConfirmationRequest */
    args?: Record<string, unknown>;
    riskLevel?: string;
    reason?: string;
  }>();

  /** 确认超时时间（可从配置读取，默认 120 秒） */
  private confirmationTimeoutMs = 120_000;

  constructor(
    private mainWindow: BrowserWindow | null = null,
    private configService: ConfigService | null = null,
  ) {
    this.loadAutoExecuteList();
    this.loadConfirmationTimeout();
  }

  /** 设置主窗口（用于发送 IPC 消息） */
  setMainWindow(window: BrowserWindow): void {
    this.mainWindow = window;
  }

  /**
   * 从 ConfigService 加载已设置为自动执行的工具列表
   * 配置键格式：tools.{toolName}.autoExecute = true
   */
  private loadAutoExecuteList(): void {
    if (!this.configService) return;
    try {
      const all = this.configService.getAll();
      this.autoExecuteTools.clear();
      for (const [key, value] of Object.entries(all)) {
        // 匹配 tools.{toolName}.autoExecute 键
        const match = /^tools\.(.+)\.autoExecute$/.exec(key);
        if (match && value === true) {
          this.autoExecuteTools.add(match[1]);
        }
      }
    } catch {
      // 配置加载失败，忽略（不阻塞启动）
    }
  }

  /**
   * 设置/取消工具的持久化自动执行
   * @param toolName 工具名
   * @param enabled true=自动执行（不再询问），false=每次询问
   */
  setAutoExecute(toolName: string, enabled: boolean): void {
    const configKey = `tools.${toolName}.autoExecute`;
    if (this.configService) {
      this.configService.set(configKey, enabled);
    }
    if (enabled) {
      this.autoExecuteTools.add(toolName);
      // 自动执行时同步清除会话内"拒绝"记忆（避免冲突）
      this.rememberedDecisions.delete(toolName);
    } else {
      this.autoExecuteTools.delete(toolName);
    }
  }

  /**
   * 获取当前自动执行的工具列表
   */
  getAutoExecuteList(): string[] {
    return Array.from(this.autoExecuteTools);
  }

  /**
   * 从 ConfigService 加载确认超时时间
   * 配置键：agent.confirmationTimeoutMs（单位毫秒，最小 30 秒，最大 600 秒）
   */
  private loadConfirmationTimeout(): void {
    if (!this.configService) return;
    try {
      const timeout = this.configService.get<number>('agent.confirmationTimeoutMs');
      if (timeout != null) {
        // L-12 修复: 使用命名常量替代魔法数字
        this.confirmationTimeoutMs = Math.min(
          ConfirmationHook.MAX_CONFIRMATION_TIMEOUT_MS,
          Math.max(ConfirmationHook.MIN_CONFIRMATION_TIMEOUT_MS, timeout),
        );
      }
    } catch {
      // 配置加载失败，使用默认值
    }
  }

  /** 设置确认超时时间（运行时更新） */
  setConfirmationTimeout(ms: number): void {
    // L-12 修复: 使用命名常量替代魔法数字
    this.confirmationTimeoutMs = Math.min(
      ConfirmationHook.MAX_CONFIRMATION_TIMEOUT_MS,
      Math.max(ConfirmationHook.MIN_CONFIRMATION_TIMEOUT_MS, ms),
    );
  }

  /** 获取当前确认超时时间 */
  getConfirmationTimeout(): number {
    return this.confirmationTimeoutMs;
  }

  /** 注入工具定义列表（用于查询风险等级） */
  setToolDefs(defs: MetonaToolDef[]): void {
    this.toolDefs.clear();
    for (const def of defs) {
      this.toolDefs.set(def.name, def);
    }
  }

  /**
   * 处理用户确认响应（由 IPC handler 调用）
   * @param remember 会话内记住决定
   * @param autoExecute 永久自动执行（持久化）
   */
  resolveConfirmation(toolCallId: string, approved: boolean, remember: boolean, autoExecute: boolean = false): void {
    const pending = this.pendingConfirmations.get(toolCallId);
    if (pending) {
      clearTimeout(pending.timer);
      pending.resolve(approved);
      // 永久自动执行（仅当用户批准时才设置）
      if (autoExecute && approved) {
        this.setAutoExecute(pending.toolName, true);
      }
      // 会话内记忆
      if (remember) {
        this.rememberedDecisions.set(pending.toolName, approved);
      }
      this.pendingConfirmations.delete(toolCallId);
    }
  }

  /**
   * 批量处理用户确认响应（由 IPC tool:confirmationResponseBatch 调用）
   *
   * 设计要点：
   * 1. 单次批量 resolve 即可处理 N 个 pending（避免逐个 IPC 往返）
   * 2. remember/autoExecute 按 toolName 去重写入，避免 Map 重复赋值
   * 3. 批量拒绝时若 remember=true 也会写入"拒绝"记忆（与单条语义一致）
   *
   * @param toolCallIds 待处理的 toolCallId 列表
   * @param approved 批准/拒绝
   * @param remember 会话内记住决定（按 toolName 去重）
   * @param autoExecute 永久自动执行（仅 approved=true 时生效，按 toolName 去重）
   * @returns 实际处理成功的 toolCallId 数组（未找到的会被跳过）
   */
  resolveConfirmationsBatch(
    toolCallIds: string[],
    approved: boolean,
    remember: boolean,
    autoExecute: boolean = false,
  ): string[] {
    const resolved: string[] = [];
    // 同一 toolName 在批量中可能多次出现，仅写入一次决策记忆
    const processedToolNames = new Set<string>();

    for (const id of toolCallIds) {
      const pending = this.pendingConfirmations.get(id);
      if (!pending) continue; // 已超时或不存在，跳过

      clearTimeout(pending.timer);
      pending.resolve(approved);
      this.pendingConfirmations.delete(id);
      resolved.push(id);

      // 按工具名去重写入决策（同一工具的多次调用共享一次决策）
      if (!processedToolNames.has(pending.toolName)) {
        processedToolNames.add(pending.toolName);
        if (autoExecute && approved) {
          this.setAutoExecute(pending.toolName, true);
        }
        if (remember) {
          this.rememberedDecisions.set(pending.toolName, approved);
        }
      }
    }
    return resolved;
  }

  /**
   * 获取当前所有等待中的确认请求（供前端批量审批 UI 拉取已积压的请求）
   *
   * 场景：并行工具触发的多个 IPC 事件可能在前端 state 中互相覆盖，
   * 前端可在弹框打开时主动调用此接口，确保拿到完整的 pending 列表。
   *
   * @returns pending 确认请求的快照（含 toolCallId/toolName/args/riskLevel/reason/expiresAt）
   */
  getPendingConfirmations(): ConfirmationRequest[] {
    const result: ConfirmationRequest[] = [];
    for (const [id, pending] of this.pendingConfirmations) {
      // 重建 ConfirmationRequest（前端需要 args 用于展示参数详情）
      // pending 已在 waitForConfirmation 中缓存完整请求信息
      result.push({
        toolCallId: id,
        toolName: pending.toolName,
        args: pending.args ?? {},
        riskLevel: pending.riskLevel ?? 'medium',
        reason: pending.reason ?? `Tool "${pending.toolName}" requires confirmation`,
        expiresAt: pending.expiresAt,
      });
    }
    return result;
  }

  async beforeExecute(toolCall: MetonaToolCall, _sessionId: string): Promise<HookResult> {
    const def = this.toolDefs.get(toolCall.name);
    if (!def) {
      // 未知工具，放行（由 ToolRegistry 处理未知工具错误）
      return { blocked: false };
    }

    // 检查是否需要确认
    const needsConfirmation = def.requiresPermission ||
      ConfirmationHook.REQUIRES_CONFIRMATION.includes(def.riskLevel);

    if (!needsConfirmation) {
      return { blocked: false };
    }

    // 优先检查持久化自动执行（跨会话生效）
    if (this.autoExecuteTools.has(toolCall.name)) {
      return { blocked: false };
    }

    // 检查是否有记住的决策
    const remembered = this.rememberedDecisions.get(toolCall.name);
    if (remembered !== undefined) {
      if (remembered) return { blocked: false };
      return { blocked: true, reason: `User previously denied tool "${toolCall.name}"` };
    }

    // 如果没有主窗口，安全起见阻止执行
    if (!this.mainWindow || this.mainWindow.isDestroyed()) {
      return { blocked: true, reason: 'Cannot request confirmation: no main window available' };
    }

    // 发送确认请求到渲染进程
    const request: ConfirmationRequest = {
      toolCallId: toolCall.id,
      toolName: toolCall.name,
      args: toolCall.args,
      riskLevel: def.riskLevel,
      reason: def.requiresPermission
        ? `Tool "${toolCall.name}" requires permission (risk: ${def.riskLevel})`
        : `Tool "${toolCall.name}" has high risk level: ${def.riskLevel}`,
    };

    // 等待用户响应（带超时）
    const approved = await this.waitForConfirmation(request);

    if (!approved) {
      return { blocked: true, reason: `User denied execution of tool "${toolCall.name}"` };
    }

    return { blocked: false };
  }

  /**
   * 等待用户确认（带超时）
   * 发送 expiresAt 到前端，供倒计时 UI 使用；超时时发送 toast 通知
   *
   * v0.3.2 修复：超时 toast 防风暴
   * 并行工具全部超时时会触发 N 个 toast，用 lastToastAt 节流（3 秒内只发 1 条），
   * 且消息改为汇总形式"工具确认超时，N 个工具未执行"。
   */
  private lastTimeoutToastAt = 0;

  private waitForConfirmation(request: ConfirmationRequest): Promise<boolean> {
    return new Promise<boolean>((resolve) => {
      const expiresAt = Date.now() + this.confirmationTimeoutMs;

      // #18 修复: 超时与用户确认的竞态条件防护
      // 场景：超时 setTimeout 回调已进入事件循环队列但尚未执行时，用户点击确认，
      // resolveConfirmation 中 clearTimeout 无法取消已排队的回调，
      // 导致用户已确认但仍弹出"超时 toast"等副作用。
      // 使用 settled 标志确保超时分支与确认分支互斥，先到者赢，另一分支直接 return。
      let settled = false;
      const safeResolve = (v: boolean) => {
        if (settled) return;
        settled = true;
        resolve(v);
      };

      // 设置超时
      const timer = setTimeout(() => {
        if (settled) return; // 已被 resolveConfirmation 处理，跳过超时副作用
        this.pendingConfirmations.delete(request.toolCallId);
        // 超时发送 toast 通知用户（3 秒节流，防止并行工具风暴）
        if (this.mainWindow && !this.mainWindow.isDestroyed()) {
          const now = Date.now();
          if (now - this.lastTimeoutToastAt > 3000) {
            this.lastTimeoutToastAt = now;
            // 统计当前还有多少 pending（含本次刚超时的）
            const pendingCount = this.pendingConfirmations.size + 1;
            const message = pendingCount > 1
              ? `工具确认超时（${Math.round(this.confirmationTimeoutMs / 1000)}秒），${pendingCount} 个工具未执行`
              : `工具确认超时（${Math.round(this.confirmationTimeoutMs / 1000)}秒），"${request.toolName}" 未执行`;
            this.mainWindow.webContents.send('toast:show', {
              type: 'warning',
              message,
            });
          }
        }
        safeResolve(false); // 超时视为拒绝
      }, this.confirmationTimeoutMs);

      this.pendingConfirmations.set(request.toolCallId, {
        resolve: safeResolve,
        timer,
        toolName: request.toolName,
        expiresAt,
        // v0.3.2 批量审批：同步缓存完整请求信息，供 getPendingConfirmations() 返回
        args: request.args,
        riskLevel: request.riskLevel,
        reason: request.reason,
      });

      // 发送确认请求到渲染进程（携带过期时间戳，供前端倒计时）
      if (this.mainWindow && !this.mainWindow.isDestroyed()) {
        this.mainWindow.webContents.send('tool:confirmationRequest', {
          ...request,
          expiresAt,
        });
      }
    });
  }

  /**
   * 清理所有等待中的确认（会话结束时调用）
   */
  clearPending(): void {
    for (const [, pending] of this.pendingConfirmations) {
      clearTimeout(pending.timer);
      pending.resolve(false);
    }
    this.pendingConfirmations.clear();
  }
}
