/**
 * File Guard — 受保护文件守卫
 *
 * 确保工作空间根目录的 MEMORY.md 只能由系统内部（WorkspaceService）管理，
 * 任何工具（read_file / write_file / search_files / run_command 等）均禁止直接读写。
 *
 * 注意：仅保护工作空间根目录的 MEMORY.md，
 * 子目录或其他位置的同名文件不受限制。
 */

import { resolve, sep } from 'path';
import { realpathSync } from 'fs';

/**
 * 受保护文件名列表（工作空间根目录）
 */
const PROTECTED_FILES = ['MEMORY.md'];

/**
 * 检查目标路径是否为工作空间根目录的受保护文件
 *
 * @param filePath 用户传入的文件路径（绝对或相对）
 * @param workspacePath 当前工作空间根路径
 * @returns true 如果路径指向受保护文件
 */
export function isProtectedWorkspaceFile(
  filePath: string,
  workspacePath: string,
): boolean {
  const resolved = resolve(workspacePath, filePath);
  const workspaceRoot = resolve(workspacePath);

  for (const protectedName of PROTECTED_FILES) {
    const protectedPath = resolve(workspaceRoot, protectedName);
    if (resolved === protectedPath) {
      return true;
    }
  }
  return false;
}

/**
 * 检查路径是否在工作空间内（防止路径遍历攻击）
 *
 * 修复前缀碰撞漏洞：`/home/user/app-evil` 不应被误判为在 `/home/user/app` 内。
 *
 * M-22 修复: 添加 realpathSync 二次校验防止符号链接逃逸
 * 攻击场景：工作空间内创建符号链接 `ln -s /etc/passwd workspace/leak.txt`，
 * 字符串校验会通过（leak.txt 在 workspace 内），但实际读取的是 /etc/passwd。
 *
 * 注意：realpathSync 在路径不存在时会抛 ENOENT，此时降级为字符串校验
 * （write_file 的目标文件可能尚不存在，无法 realpath）。
 *
 * @see project_memory.md — sandbox validatePath must perform realpathSync secondary check
 * @param filePath 用户传入的文件路径
 * @param workspacePath 当前工作空间根路径
 * @returns true 如果路径在工作空间内
 */
export function isPathWithinWorkspace(
  filePath: string,
  workspacePath: string,
): boolean {
  const resolved = resolve(workspacePath, filePath);
  const workspaceRoot = resolve(workspacePath);

  // 第一层：字符串前缀校验（快速路径）
  const stringCheck = resolved === workspaceRoot || resolved.startsWith(workspaceRoot + sep);
  if (!stringCheck) return false;

  // 第二层：realpathSync 二次校验（防范符号链接逃逸）
  // 仅对实际存在的路径做 realpath 校验；不存在的路径（如 write_file 目标）降级为字符串校验
  try {
    const realResolved = realpathSync(resolved);
    const realWorkspaceRoot = realpathSync(workspaceRoot);
    return realResolved === realWorkspaceRoot || realResolved.startsWith(realWorkspaceRoot + sep);
  } catch {
    // 路径不存在（ENOENT）或 realpath 失败 → 降级为字符串校验结果
    return stringCheck;
  }
}

/**
 * 检查命令字符串是否尝试访问工作空间根目录的受保护文件
 *
 * 用于 run_command 工具的命令校验。
 * 仅匹配直接引用的 MEMORY.md（前面是命令起始/空白/引号/分号/管道），
 * 不拦截子目录路径中的同名文件（如 subdir/MEMORY.md 或 subdir\MEMORY.md）。
 *
 * 注意：run_command 的工作目录固定为 workspacePath，因此裸引用 MEMORY.md
 * 等价于工作空间根目录的 MEMORY.md。
 *
 * @param command Shell 命令字符串
 * @returns true 如果命令直接引用了受保护文件名
 */
export function commandTouchesProtectedFile(command: string): boolean {
  const lowerCmd = command.toLowerCase();
  for (const protectedName of PROTECTED_FILES) {
    const lowerName = protectedName.toLowerCase();
    // 前面是起始/空白/引号/分号/管道/&/>；后面是结束/空白/引号/分号/管道/&/</>
    // 这样 subdir/MEMORY.md 和 subdir\MEMORY.md 不会被匹配（前面是 / 或 \）
    const escaped = lowerName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const regex = new RegExp(`(?:^|[\\s"'|;&>])${escaped}(?:$|[\\s"'|;&<])`, 'i');
    if (regex.test(lowerCmd)) {
      return true;
    }
  }
  return false;
}

// ===== 共享工具函数（v0.3.2 抽取，消除 filesystem.ts 与 file-editor.ts 的重复）=====

/**
 * 共享路径解析 + 安全校验
 *
 * 合并两层安全检查：
 * 1. isPathWithinWorkspace — 路径遍历防护（含符号链接 realpathSync 二次校验）
 * 2. isProtectedWorkspaceFile — MEMORY.md 拦截
 *
 * @param filePath 用户传入的文件路径（绝对或相对）
 * @param workspacePath 当前工作空间根路径
 * @returns 解析后的绝对路径
 * @throws Error 路径越界或访问受保护文件时抛出
 */
export function safeResolvePath(filePath: string, workspacePath: string): string {
  const resolved = resolve(workspacePath, filePath);
  // 安全检查：路径遍历防护（修复前缀碰撞漏洞）
  if (!isPathWithinWorkspace(filePath, workspacePath)) {
    throw new Error(`Path traversal detected: ${filePath}`);
  }
  // 受保护文件检查：MEMORY.md 仅由系统内部管理
  if (isProtectedWorkspaceFile(filePath, workspacePath)) {
    throw new Error('Access denied: MEMORY.md is managed by the memory system and cannot be accessed via file tools');
  }
  return resolved;
}

/**
 * 共享 glob 匹配（简易通配符 → 正则）
 *
 * 支持 `*`（任意字符序列）和 `?`（单字符），大小写不敏感。
 * 其他正则元字符会被转义。
 *
 * @param name 待匹配的文件名
 * @param glob 通配符模式（如 "*.ts"、"test?.js"）
 * @returns 是否匹配
 */
export function matchGlob(name: string, glob: string): boolean {
  const pattern = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
  return new RegExp(`^${pattern}$`, 'i').test(name);
}

/**
 * F2-4: 多 glob 匹配（逗号分隔）
 *
 * 支持 "*.ts,*.js,*.tsx" 形式的多 glob 匹配，任一匹配即通过。
 * 单个 glob 时等价于 matchGlob。空字符串或空白字符串视为匹配所有。
 *
 * @param name 待匹配的文件名
 * @param globStr 通配符模式字符串（支持逗号分隔多 glob）
 * @returns 是否匹配任一 glob
 */
export function matchAnyGlob(name: string, globStr: string): boolean {
  // 按逗号分割，去除空白，过滤空字符串
  const globs = globStr.split(',').map((g) => g.trim()).filter((g) => g.length > 0);
  if (globs.length === 0) return true; // 空字符串视为匹配所有
  for (const g of globs) {
    if (matchGlob(name, g)) return true;
  }
  return false;
}

/**
 * 共享错误提取
 *
 * 统一从 unknown 错误对象中提取 message 字符串，可选附加 stderr 信息
 *
 * F4-2: 支持 optional stderr 参数，用于 child_process 错误（git/command）
 *
 * @param error catch 块中的 unknown 错误
 * @param includeStderr 是否尝试从 error.stderr 提取 stderr 信息（默认 false）
 * @returns 错误消息字符串
 */
export function extractErrorMessage(error: unknown, includeStderr = false): string {
  if (error instanceof Error) {
    if (includeStderr) {
      const stderr = (error as Error & { stderr?: string }).stderr ?? '';
      return stderr ? `${error.message}\n${stderr}` : error.message;
    }
    return error.message;
  }
  return String(error);
}

/**
 * F2-1: 智能文件编码检测与解码
 *
 * 支持 BOM 检测（UTF-8 / UTF-16 LE / UTF-16 BE）和无 BOM 时的编码推断
 * （UTF-8 strict → GBK → UTF-8 loose 三级降级）。
 *
 * 解决 Windows 中文环境 GBK 文件读取乱码问题，以及 UTF-16 文件读取问题。
 *
 * @param buffer 文件/命令输出的原始字节
 * @returns 解码后的文本和检测到的编码名（utf-8 / utf-8-bom / utf-16le / utf-16be / gbk / utf-8-loose）
 */
export function decodeBufferWithDetection(buffer: Buffer): { content: string; encoding: string } {
  if (buffer.length === 0) {
    return { content: '', encoding: 'utf-8' };
  }

  // BOM 检测
  // UTF-8 BOM: EF BB BF
  if (buffer.length >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
    return { content: buffer.slice(3).toString('utf-8'), encoding: 'utf-8-bom' };
  }
  // UTF-16 LE BOM: FF FE
  if (buffer.length >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
    return { content: buffer.slice(2).toString('utf16le'), encoding: 'utf-16le' };
  }
  // UTF-16 BE BOM: FE FF
  if (buffer.length >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
    const body = buffer.slice(2);
    // 偶数长度保护（UTF-16 每字符 2 字节）
    const safe = body.length % 2 === 0 ? body : body.slice(0, body.length - 1);
    const swapped = Buffer.from(safe); // 复制避免修改原 buffer
    swapped.swap16(); // BE → LE 字节交换
    return { content: swapped.toString('utf16le'), encoding: 'utf-16be' };
  }

  // 无 BOM：UTF-8 strict → GBK → UTF-8 loose 三级降级
  try {
    return { content: new TextDecoder('utf-8', { fatal: true }).decode(buffer), encoding: 'utf-8' };
  } catch {
    try {
      return { content: new TextDecoder('gbk').decode(buffer), encoding: 'gbk' };
    } catch {
      return { content: buffer.toString('utf-8'), encoding: 'utf-8-loose' };
    }
  }
}

/**
 * 共享文件大小限制常量
 *
 * read_file/write_file/file_editor 共用，防止 OOM
 */
export const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB

/**
 * 共享超时常量（v0.3.2 统一文件工具 timeoutMs）
 */
export const FILE_TOOL_TIMEOUT_MS = 15_000;

/**
 * 共享单行最大长度（防止超长行爆 token）
 */
export const MAX_LINE_LENGTH = 10_000;
