import { readFile, stat, writeFile, rename, readdir, unlink, lstat, realpath } from 'fs/promises'
import { join, extname, dirname, basename } from 'path'
import { randomBytes } from 'crypto'
import type { ReadFileResult, SaveFileResult, FileNode } from '../shared/types'
import { MAX_FILE_SIZE, ALLOWED_EXTENSIONS, SKIP_DIRS } from '../shared/constants'

const ALLOWED_EXTENSIONS_SET = new Set<string>(ALLOWED_EXTENSIONS)

export async function readFileContent(filePath: string): Promise<ReadFileResult> {
  try {
    const fileStat = await stat(filePath)
    if (fileStat.size > MAX_FILE_SIZE) {
      return { success: false, error: `文件过大（${(fileStat.size / 1024 / 1024).toFixed(1)} MB），暂不支持超过 20MB 的文件` }
    }
    // L-01: 检测并剥离 BOM（UTF-8 FEFF / UTF-16 LE FFFE / UTF-16 BE FEFF）
    const buffer = await readFile(filePath)
    let content: string
    if (buffer.length >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
      // UTF-16 BE: swap bytes to LE 再解码
      const swapped = Buffer.allocUnsafe(buffer.length)
      buffer.copy(swapped)
      swapped.swap16()
      content = swapped.toString('utf-16le')
      if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1)
    } else if (buffer.length >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
      // UTF-16 LE
      content = buffer.toString('utf-16le')
      if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1)
    } else {
      // UTF-8 (含 FEFF BOM 剥离)
      content = buffer.toString('utf-8')
      if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1)
    }
    return { success: true, content }
  } catch (err) {
    return { success: false, error: (err as Error).message }
  }
}

// C-02: 临时文件写到与目标相同目录（避免跨盘 rename 失败）
// M-01: 使用随机 hex 后缀替代 Date.now()，防止本地攻击者预创建 symlink
export async function saveFileContent(filePath: string, content: string): Promise<SaveFileResult> {
  const randomSuffix = randomBytes(8).toString('hex')
  const tmpFile = join(dirname(filePath), `.marklite-tmp-${randomSuffix}-${basename(filePath)}`)
  try {
    await writeFile(tmpFile, content, 'utf-8')
    await rename(tmpFile, filePath)
    return { success: true, filePath }
  } catch (err) {
    // 清理残留临时文件
    try { await unlink(tmpFile) } catch { /* ignore */ }
    return { success: false, error: (err as Error).message }
  }
}

// M-05: 递归深度限制 + 错误边界 + 符号链接循环检测
export async function buildDirTree(
  dirPath: string,
  depth = 0,
  maxDepth = 10,
  visited?: Set<string>
): Promise<FileNode[]> {
  if (depth > maxDepth) return []

  if (!visited) visited = new Set<string>()

  // 检测符号链接循环
  let realPath: string
  try {
    const ls = await lstat(dirPath)
    if (ls.isSymbolicLink()) return []
    realPath = await realpath(dirPath)
  } catch {
    return []
  }
  if (visited.has(realPath)) return []
  visited.add(realPath)

  let entries
  try {
    entries = await readdir(dirPath, { withFileTypes: true })
  } catch {
    return []
  }

  entries.sort((a, b) => {
    if (a.isDirectory() && !b.isDirectory()) return -1
    if (!a.isDirectory() && b.isDirectory()) return 1
    return a.name.localeCompare(b.name)
  })

  const children: FileNode[] = []
  for (const entry of entries) {
    if (SKIP_DIRS.has(entry.name)) continue
    if (entry.name.startsWith('.')) continue

    const childPath = join(dirPath, entry.name)
    if (entry.isDirectory()) {
      const subChildren = await buildDirTree(childPath, depth + 1, maxDepth, visited)
      if (subChildren.length > 0) {
        children.push({ name: entry.name, path: childPath, type: 'dir', children: subChildren })
      }
    } else {
      const ext = extname(entry.name).toLowerCase()
      if (ALLOWED_EXTENSIONS_SET.has(ext)) {
        children.push({ name: entry.name, path: childPath, type: 'file' })
      }
    }
  }
  return children
}
