/**
 * Policy Engine — 权限策略引擎
 *
 * 三级权限模型：Read / Write / External Action
 *
 * v0.3.0 增强：
 * - 实现 maxFrequency 频率限制（滑动窗口算法）
 * - 添加 checkFrequency 和 recordCall 方法
 * - 添加 cleanupFrequencyRecords 防止内存泄漏
 *
 * @see docs/生产级通用 AI Agent 智能体桌面应用：完整设计与构建指南.html — 第十章
 */

export enum PermissionLevel {
  READ = 'read',
  WRITE = 'write',
  EXTERNAL_ACTION = 'external',
}

export interface PermissionPolicy {
  toolName: string;
  requiredLevel: PermissionLevel;
  allowedPatterns?: RegExp[];
  deniedPatterns?: RegExp[];
  maxFrequency?: number;
  requireConfirmation?: boolean;
}

export const DEFAULT_POLICIES: PermissionPolicy[] = [
  // v0.3.0 修复：deniedPatterns 使用 (?:\/|["'\s,}]|$) 匹配，
  // 覆盖 /etc/ 和 /etc（无尾斜杠，在 JSON 字符串中后跟引号的情况）
  // H-5 修复: 移除 /MEMORY\.md/i 粗粒度正则 — 之前会误拦子目录的 MEMORY.md
  // 改为在 engine.ts executeToolSafely 中进行精确的根目录校验（仅保护 workspacePath/MEMORY.md）
  // @see project_memory.md — Only the MEMORY.md in the workspace root directory is protected
  { toolName: 'read_file', requiredLevel: PermissionLevel.READ, deniedPatterns: [/\/etc(?:\/|["'\s,}]|$)/, /\/proc(?:\/|["'\s,}]|$)/, /C:\\Windows\\/i, /C:\\System32\\/i] },
  { toolName: 'web_search', requiredLevel: PermissionLevel.READ, maxFrequency: 10 },
  { toolName: 'list_directory', requiredLevel: PermissionLevel.READ },
  { toolName: 'search_files', requiredLevel: PermissionLevel.READ },
  { toolName: 'memory_search', requiredLevel: PermissionLevel.READ },
  { toolName: 'write_file', requiredLevel: PermissionLevel.WRITE, deniedPatterns: [/\/etc(?:\/|["'\s,}]|$)/, /\/proc(?:\/|["'\s,}]|$)/, /\/System(?:\/|["'\s,}]|$)/, /C:\\Windows\\/i, /C:\\System32\\/i], requireConfirmation: true, maxFrequency: 5 },
  { toolName: 'memory_store', requiredLevel: PermissionLevel.WRITE },
  { toolName: 'run_command', requiredLevel: PermissionLevel.EXTERNAL_ACTION, deniedPatterns: [/MEMORY\.md/i], requireConfirmation: true, maxFrequency: 10 },
  { toolName: 'web_fetch', requiredLevel: PermissionLevel.READ },
  // web_browser — 统一浏览器工具（合并自 9 个独立 browser_* 工具）
  // 由于该工具可执行 JS、点击元素等高风险操作，统一设为 EXTERNAL_ACTION
  { toolName: 'web_browser', requiredLevel: PermissionLevel.EXTERNAL_ACTION, requireConfirmation: true, maxFrequency: 20 },
  // v0.3.0 修复: 补全缺失的工具策略 — 之前这5个工具未配置策略，导致被 PolicyEngine 拦截
  // file_editor — 精准文件编辑（WRITE），与 write_file 同级安全约束
  { toolName: 'file_editor', requiredLevel: PermissionLevel.WRITE, deniedPatterns: [/\/etc(?:\/|["'\s,}]|$)/, /\/proc(?:\/|["'\s,}]|$)/, /\/System(?:\/|["'\s,}]|$)/, /C:\\Windows\\/i, /C:\\System32\\/i], requireConfirmation: true, maxFrequency: 10 },
  // code_search — 基于 ripgrep 的只读搜索（READ）
  { toolName: 'code_search', requiredLevel: PermissionLevel.READ },
  // diff_viewer — 文件/文本差异对比（只读，READ）
  { toolName: 'diff_viewer', requiredLevel: PermissionLevel.READ },
  // task_manager — 任务管理（数据库读写，低风险 WRITE）
  { toolName: 'task_manager', requiredLevel: PermissionLevel.WRITE },
  // delegate_task — 子任务委派（启动 SubAgent，EXTERNAL_ACTION）
  { toolName: 'delegate_task', requiredLevel: PermissionLevel.EXTERNAL_ACTION, requireConfirmation: false, maxFrequency: 5 },
  // C-7 修复: MCP 工具通配符策略 — MCP 工具名称动态生成（mcp_{serverName}_{toolName}）
  // 无法预先配置精确策略，使用 mcp_* 通配符匹配所有 MCP 工具
  // @see project_memory.md — All tools must have a configured policy in DEFAULT_POLICIES
  { toolName: 'mcp_*', requiredLevel: PermissionLevel.EXTERNAL_ACTION, requireConfirmation: true, maxFrequency: 20 },

  // v0.3.1: Git 工具集（4 个）
  { toolName: 'git_status', requiredLevel: PermissionLevel.READ },
  { toolName: 'git_diff', requiredLevel: PermissionLevel.READ },
  { toolName: 'git_log', requiredLevel: PermissionLevel.READ },
  { toolName: 'git_commit', requiredLevel: PermissionLevel.WRITE, requireConfirmation: true, maxFrequency: 10 },

  // v0.3.1: 开发工具集（3 个）
  { toolName: 'lint_code', requiredLevel: PermissionLevel.READ },
  { toolName: 'run_tests', requiredLevel: PermissionLevel.READ },
  { toolName: 'project_info', requiredLevel: PermissionLevel.READ },

  // v0.3.1: HTTP 请求工具（1 个）
  { toolName: 'http_request', requiredLevel: PermissionLevel.READ, maxFrequency: 20 },

  // v0.3.1: 结构化思考工具（1 个）— 无副作用
  { toolName: 'think', requiredLevel: PermissionLevel.READ },

  // v0.3.1: 图片查看工具（1 个）— 只读
  { toolName: 'view_image', requiredLevel: PermissionLevel.READ },

  // v0.3.2: 文件删除工具（1 个）— 破坏性操作，必须确认
  { toolName: 'delete_file', requiredLevel: PermissionLevel.WRITE, requireConfirmation: true, maxFrequency: 30 },

  // v0.3.3: 文件移动/重命名工具（1 个）— 可能覆盖目标，需确认
  { toolName: 'file_move', requiredLevel: PermissionLevel.WRITE, requireConfirmation: true, maxFrequency: 30 },

  // v0.3.3: 文件信息查询工具（1 个）— 只读
  { toolName: 'file_info', requiredLevel: PermissionLevel.READ },
];

export class PolicyEngine {
  private policies: Map<string, PermissionPolicy> = new Map();

  /** v0.3.0: 工具调用频率追踪 — 工具名 -> 调用时间戳列表 */
  private callFrequency: Map<string, number[]> = new Map();

  /** v0.3.0: 频率限制的时间窗口（1分钟 = 60秒） */
  private readonly FREQ_WINDOW_MS = 60_000;

  /**
   * v0.3.0 修复：customPolicies 与 DEFAULT_POLICIES 合并而非完全覆盖
   *
   * 合并策略：customPolicies 中的字段覆盖默认策略的同名字段，
   * 未指定的字段保留默认值（如 deniedPatterns 等安全配置不会被丢失）
   */
  constructor(customPolicies: PermissionPolicy[] = []) {
    for (const policy of DEFAULT_POLICIES) {
      this.policies.set(policy.toolName, { ...policy });
    }
    for (const policy of customPolicies) {
      const existing = this.policies.get(policy.toolName);
      if (existing) {
        // v0.3.0 修复：合并而非替换，保留默认的安全配置（如 deniedPatterns）
        this.policies.set(policy.toolName, { ...existing, ...policy });
      } else {
        this.policies.set(policy.toolName, policy);
      }
    }
  }

  checkAuthorization(toolName: string, args: Record<string, unknown>): {
    authorized: boolean;
    reason?: string;
    level: PermissionLevel;
    requiresConfirmation: boolean;
  } {
    let policy = this.policies.get(toolName);

    // C-7 修复: 支持通配符策略匹配（如 mcp_* 匹配所有 MCP 工具）
    // MCP 工具名称动态生成（mcp_{serverName}_{toolName}），无法预先配置精确策略
    if (!policy) {
      for (const [pattern, p] of this.policies) {
        if (pattern.endsWith('*') && toolName.startsWith(pattern.slice(0, -1))) {
          policy = p;
          break;
        }
      }
    }

    if (!policy) {
      return {
        authorized: false,
        reason: `No policy configured for tool: ${toolName}`,
        level: PermissionLevel.EXTERNAL_ACTION,
        requiresConfirmation: true,
      };
    }

    // v0.3.0 修复：使用 try-catch 防止循环引用导致 JSON.stringify 抛错
    let argsStr: string;
    try {
      argsStr = JSON.stringify(args);
    } catch {
      // 循环引用等异常情况，降级为 toString
      argsStr = String(args);
    }

    // #14 修复: 深度递归扫描所有字符串字段值，防止 Unicode 转义/嵌套对象/字符串拼接绕过
    // 原 JSON.stringify 后整体正则匹配可被 \u0029 编码、嵌套对象伪装、正则注入等方式绕过
    // 现对每个字符串字段值单独匹配，同时保留整体 argsStr 兜底匹配
    const stringValues = this.deepScanStrings(args);

    // v0.2.0: allowedPatterns 白名单校验 — 若定义了白名单，参数必须匹配其中之一
    // #14: 字段级匹配 + 整体兜底，任一匹配即通过
    if (policy.allowedPatterns && policy.allowedPatterns.length > 0) {
      let matchedAllowed = false;
      // 先做字段级匹配
      for (const val of stringValues) {
        for (const pattern of policy.allowedPatterns) {
          if (pattern.test(val)) {
            matchedAllowed = true;
            break;
          }
        }
        if (matchedAllowed) break;
      }
      // 兜底：整体 argsStr 匹配
      if (!matchedAllowed) {
        for (const pattern of policy.allowedPatterns) {
          if (pattern.test(argsStr)) {
            matchedAllowed = true;
            break;
          }
        }
      }
      if (!matchedAllowed) {
        return {
          authorized: false,
          reason: 'Arguments do not match any allowed pattern',
          level: policy.requiredLevel,
          requiresConfirmation: policy.requireConfirmation ?? false,
        };
      }
    }

    // #14 修复: deniedPatterns 字段级深度扫描 — 对每个字符串值单独匹配
    // 防止危险内容隐藏在嵌套对象或 Unicode 编码中绕过整体 stringify 匹配
    if (policy.deniedPatterns) {
      for (const val of stringValues) {
        for (const pattern of policy.deniedPatterns) {
          if (pattern.test(val)) {
            return {
              authorized: false,
              reason: 'Command blocked by security policy',
              level: policy.requiredLevel,
              requiresConfirmation: false,
            };
          }
        }
      }
      // 兜底：整体 argsStr 匹配（保留原有行为，捕获跨字段拼接的危险模式）
      for (const pattern of policy.deniedPatterns) {
        if (pattern.test(argsStr)) {
          return {
            authorized: false,
            reason: 'Command blocked by security policy',
            level: policy.requiredLevel,
            requiresConfirmation: false,
          };
        }
      }
    }

    // v0.3.0: 频率限制检查
    if (policy.maxFrequency !== undefined) {
      const freqCheck = this.checkFrequency(toolName, policy.maxFrequency);
      if (!freqCheck.allowed) {
        return {
          authorized: false,
          reason: freqCheck.reason,
          level: policy.requiredLevel,
          requiresConfirmation: false,
        };
      }
    }

    return {
      authorized: true,
      level: policy.requiredLevel,
      requiresConfirmation: policy.requireConfirmation ?? false,
    };
  }

  /**
   * v0.3.0: 频率限制检查（滑动窗口算法）
   *
   * 检查指定工具在时间窗口内的调用次数是否超过限制。
   * 注意：此方法仅检查，不记录调用。调用成功后需调用 recordCall()。
   *
   * v0.3.0 修复：
   * - 将 validCalls 写回 Map，避免 callFrequency 数组无限增长（内存泄漏）
   *
   * @param toolName 工具名称
   * @param maxFreq 最大频率（每分钟）
   * @returns 检查结果
   */
  checkFrequency(toolName: string, maxFreq?: number): { allowed: boolean; reason?: string } {
    const policy = this.policies.get(toolName);
    const limit = maxFreq ?? policy?.maxFrequency;
    if (limit === undefined) return { allowed: true };

    const now = Date.now();
    const calls = this.callFrequency.get(toolName) ?? [];
    // 移除时间窗口外的调用记录
    const validCalls = calls.filter((t) => now - t < this.FREQ_WINDOW_MS);

    // v0.3.0 修复：将清理后的 validCalls 写回 Map，避免数组无限增长
    if (validCalls.length !== calls.length) {
      this.callFrequency.set(toolName, validCalls);
    }

    if (validCalls.length >= limit) {
      return {
        allowed: false,
        reason: `Rate limit exceeded for ${toolName}: max ${limit} calls per minute (current: ${validCalls.length})`,
      };
    }
    return { allowed: true };
  }

  /**
   * v0.3.0: 记录工具调用（工具成功执行后调用）
   *
   * v0.3.0 修复：同时清理过期记录，防止数组无限增长
   *
   * @param toolName 工具名称
   */
  recordCall(toolName: string): void {
    const now = Date.now();
    const calls = this.callFrequency.get(toolName) ?? [];
    // v0.3.0 修复：记录新调用时同时清理过期记录
    const validCalls = calls.filter((t) => now - t < this.FREQ_WINDOW_MS);
    validCalls.push(now);
    this.callFrequency.set(toolName, validCalls);
  }

  // v0.3.0 修复: cleanupFrequencyRecords 已删除 — checkFrequency 和 recordCall 已做内联清理，
  // 该方法属于死代码，删除以减少维护负担

  /**
   * #14 修复: 深度递归扫描对象，提取所有字符串字段值
   *
   * 替代 JSON.stringify 后整体正则匹配的方式，防止：
   * - Unicode 转义绕过（\u0029 等编码）
   * - 嵌套对象伪装（{a: {b: 'dangerous'}}）
   * - 字符串拼接绕过（'rm ' + '-rf /' 在 stringify 后是合法字符串）
   * - 正则注入（输入中包含正则元字符破坏匹配逻辑）
   *
   * @param obj 待扫描的对象
   * @returns 所有字符串字段值的数组
   */
  // 审查修复: 添加 visited Set 参数防止循环引用导致无限递归栈溢出
  private deepScanStrings(obj: unknown, visited: Set<unknown> = new Set()): string[] {
    const results: string[] = [];
    if (typeof obj === 'string') {
      results.push(obj);
    } else if (Array.isArray(obj)) {
      if (visited.has(obj)) return results;
      visited.add(obj);
      for (const item of obj) {
        results.push(...this.deepScanStrings(item, visited));
      }
    } else if (obj && typeof obj === 'object') {
      if (visited.has(obj)) return results;
      visited.add(obj);
      for (const v of Object.values(obj)) {
        results.push(...this.deepScanStrings(v, visited));
      }
    }
    return results;
  }
}
