/**
 * 音频问题扫描与修复脚本
 * 
 * 功能：
 * 1. 扫描所有章节，检测语速异常（音频时长与字数不匹配）
 * 2. 检测 WAV 文件被错误存为 MP3 的问题
 * 3. 检测 FFmpeg concat 合并丢失段的问题
 * 4. 检测 level=1 章节合并音频与子节时长总和不匹配
 * 5. 检测 audioDuration 为 0 但有 audioUrl 的章节
 * 6. 提供自动修复（重新合并分段、上传、更新数据库）
 * 
 * 用法：
 *   npx tsx scripts/fix-audio-issues.ts              # 扫描所有问题
 *   npx tsx scripts/fix-audio-issues.ts --fix         # 扫描并自动修复
 *   npx tsx scripts/fix-audio-issues.ts --book 5      # 只扫描指定书籍
 *   npx tsx scripts/fix-audio-issues.ts --chapter 11  # 只扫描指定章节
 *   npx tsx scripts/fix-audio-issues.ts --fix --chapter 11  # 修复指定章节
 *   npx tsx scripts/fix-audio-issues.ts --merge-all   # 重新合并所有 level=1 章的音频
 * 
 * 异常判定规则：
 *   - 语速 > 10字/秒 → 严重异常（可能丢失了大部分音频）
 *   - 语速 6-10字/秒 → 轻微异常（可能丢失了部分音频）
 *   - 语速 3-6字/秒 → 正常
 *   - 音频时长为0但有 audioUrl → 需要补充时长
 *   - level=1 章合并音频时长 < 子节时长总和 * 0.8 → 合并不完整
 */

import { PrismaClient } from '@prisma/client';
import { exec } from 'child_process';
import { promisify } from 'util';
import fs from 'fs';
import path from 'path';
import axios from 'axios';

const execAsync = promisify(exec);
const prisma = new PrismaClient();

// ============ 配置 ============

const SEVERE_RATE = 10;         // 严重异常阈值（字/秒）
const MILD_RATE = 6;            // 轻微异常阈值（字/秒）
const CHARS_PER_SECOND = 4;     // 估算用的标准语速
const MERGE_TOLERANCE = 0.8;    // 合并音频时长容差（低于子节总时长的80%视为异常）

// ============ 解析命令行参数 ============

const args = process.argv.slice(2);
const shouldFix = args.includes('--fix');
const mergeAll = args.includes('--merge-all');
const bookArg = args.find(a => a.startsWith('--book'));
const chapterArg = args.find(a => a.startsWith('--chapter'));
const bookId = bookArg ? parseInt(bookArg.split('=')[1] || args[args.indexOf(bookArg) + 1]) : null;
const chapterId = chapterArg ? parseInt(chapterArg.split('=')[1] || args[args.indexOf(chapterArg) + 1]) : null;

// ============ 类型定义 ============

type IssueSeverity = 'critical' | 'mild' | 'missing_duration' | 'format_mismatch' | 'merge_incomplete';

interface AudioIssue {
  chapterId: number;
  bookId: number;
  bookTitle: string;
  chapterTitle: string;
  level: number;
  wordCount: number;
  audioDuration: number;
  audioUrl: string;
  rate: number;
  severity: IssueSeverity;
  description: string;
  localSegments?: string[];
  segmentTotalDuration?: number;
  expectedDuration?: number; // 预估应有的时长
  fuzzyMatchedDir?: string; // 模糊匹配的可能目录（需人工确认）
}

// ============ 工具函数 ============

async function getLocalAudioDuration(filePath: string): Promise<number> {
  try {
    const { stdout } = await execAsync(
      `ffprobe -i "${filePath}" -show_entries format=duration -v quiet -of csv="p=0"`,
      { timeout: 10000 }
    );
    return Math.round((parseFloat(stdout.trim()) || 0) * 10) / 10;
  } catch {
    return 0;
  }
}

async function getRemoteAudioDuration(url: string): Promise<number> {
  try {
    const { stdout } = await execAsync(
      `ffprobe -i "${url}" -show_entries format=duration -v quiet -of csv="p=0"`,
      { timeout: 15000 }
    );
    const duration = parseFloat(stdout.trim());
    if (duration > 0) return Math.round(duration * 10) / 10;
  } catch {}

  try {
    const tempDir = path.join(process.cwd(), 'temp');
    if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true });
    const tempFile = path.join(tempDir, `check_${Date.now()}.tmp`);
    const response = await axios.get(url, { responseType: 'arraybuffer', timeout: 30000 });
    fs.writeFileSync(tempFile, response.data);
    const duration = await getLocalAudioDuration(tempFile);
    try { fs.unlinkSync(tempFile); } catch {}
    return duration;
  } catch {
    return 0;
  }
}

function isWavFile(filePath: string): boolean {
  try {
    if (!fs.existsSync(filePath)) return false;
    const buffer = Buffer.alloc(12);
    const fd = fs.openSync(filePath, 'r');
    fs.readSync(fd, buffer, 0, 12, 0);
    fs.closeSync(fd);
    return buffer.toString('ascii', 0, 4) === 'RIFF' && buffer.toString('ascii', 8, 12) === 'WAVE';
  } catch {
    return false;
  }
}

function resolveAudioUrlToLocal(audioUrl: string): string | null {
  if (!audioUrl) return null;
  if (audioUrl.startsWith('http')) return null;
  const relativePath = audioUrl.replace(/^\//, '');
  const absolutePath = path.join(process.cwd(), relativePath);
  return fs.existsSync(absolutePath) ? absolutePath : null;
}

function findLocalSegments(audioDir: string): string[] {
  if (!fs.existsSync(audioDir)) return [];
  const segments: string[] = [];
  const files = fs.readdirSync(audioDir).sort();
  for (const file of files) {
    if (file.match(/^segment_\d+\.(wav|mp3)$/)) {
      segments.push(path.join(audioDir, file));
    }
  }
  return segments;
}

function inferAudioDir(audioUrl: string): string | null {
  if (!audioUrl) return null;
  const match = audioUrl.match(/\/uploads\/([^/]+)\//);
  if (match) {
    return path.join(process.cwd(), 'uploads', match[1]);
  }
  return null;
}

/**
 * 扫描 uploads 目录，查找所有包含 segment_*.wav 的目录
 * 返回 Map<目录路径, 分段文件列表>
 */
function scanAllSegmentDirs(): Map<string, string[]> {
  const uploadsDir = path.join(process.cwd(), 'uploads');
  if (!fs.existsSync(uploadsDir)) return new Map();

  const result = new Map<string, string[]>();

  try {
    const dirs = fs.readdirSync(uploadsDir);
    for (const dir of dirs) {
      const dirPath = path.join(uploadsDir, dir);
      try {
        if (!fs.statSync(dirPath).isDirectory()) continue;
      } catch { continue; }

      const segments = findLocalSegments(dirPath);
      if (segments.length > 0) {
        result.set(dirPath, segments);
      }
    }
  } catch {}

  return result;
}

/**
 * 通过分段文件总时长与数据库章节字数对比，找到最匹配的目录
 * ⚠️ 此方法仅作参考，不自动使用——需要人工确认后再修复
 */
function findBestMatchingSegmentDir(
  segmentDirs: Map<string, string[]>,
  wordCount: number,
  currentDuration: number
): { dir: string; segments: string[]; totalDuration: number } | null {
  const expectedDuration = wordCount / CHARS_PER_SECOND;
  let bestMatch: { dir: string; segments: string[]; totalDuration: number; diff: number } | null = null;

  for (const [dir, segments] of segmentDirs) {
    // 计算分段总时长（同步快速估算）
    let totalSize = 0;
    for (const seg of segments) {
      try { totalSize += fs.statSync(seg).size; } catch {}
    }
    // WAV 文件大约 176KB/s（16bit 22kHz 立体声）或 88KB/s（单声道）
    const estimatedDuration = totalSize / 176000; // 粗估

    // 寻找估算时长接近预期时长的
    const diff = Math.abs(estimatedDuration - expectedDuration);
    if (!bestMatch || diff < bestMatch.diff) {
      bestMatch = { dir, segments, totalDuration: estimatedDuration, diff };
    }
  }

  return bestMatch ? { dir: bestMatch.dir, segments: bestMatch.segments, totalDuration: bestMatch.totalDuration } : null;
}

async function mergeAndUpload(
  segmentFiles: string[],
  outputDir: string,
  chapterId: number
): Promise<{ url: string; duration: number } | null> {
  if (segmentFiles.length === 0) return null;

  try {
    const listFile = path.join(outputDir, 'fix_concat_list.txt');
    const listContent = segmentFiles.map(f => {
      const relativePath = path.relative(outputDir, f).replace(/\\/g, '/');
      return `file '${relativePath}'`;
    }).join('\n');
    fs.writeFileSync(listFile, listContent);

    const outputFile = path.join(outputDir, `output_fixed_${Date.now()}.mp3`);
    const cmd = `ffmpeg -f concat -safe 0 -i "${listFile}" -c:a libmp3lame -b:a 192k -y "${outputFile}"`;
    console.log(`  🔧 执行: ${cmd}`);
    await execAsync(cmd, { timeout: 300000, cwd: outputDir });

    const duration = await getLocalAudioDuration(outputFile);

    const { storageService } = await import('../src/services/storage.service');
    const audioId = `fix_${chapterId}_${Date.now()}`;
    const finalUrl = await storageService.uploadAudio(outputFile, audioId);

    try { fs.unlinkSync(listFile); } catch {}
    try { fs.unlinkSync(outputFile); } catch {}

    console.log(`  ✅ 合并上传成功: ${finalUrl}, 时长: ${duration}s`);
    return { url: finalUrl, duration };
  } catch (error: any) {
    console.error(`  ❌ 合并失败: ${error.message}`);
    return null;
  }
}

async function transcodeAndUpload(
  localFile: string,
  chapterId: number
): Promise<{ url: string; duration: number } | null> {
  try {
    const outputDir = path.dirname(localFile);
    const outputFile = path.join(outputDir, `transcoded_${Date.now()}.mp3`);
    const cmd = `ffmpeg -i "${localFile}" -c:a libmp3lame -b:a 192k -y "${outputFile}"`;
    console.log(`  🔧 转码: ${cmd}`);
    await execAsync(cmd, { timeout: 120000 });

    const duration = await getLocalAudioDuration(outputFile);

    const { storageService } = await import('../src/services/storage.service');
    const audioId = `fix_${chapterId}_${Date.now()}`;
    const finalUrl = await storageService.uploadAudio(outputFile, audioId);

    try { fs.unlinkSync(outputFile); } catch {}

    console.log(`  ✅ 转码上传成功: ${finalUrl}, 时长: ${duration}s`);
    return { url: finalUrl, duration };
  } catch (error: any) {
    console.error(`  ❌ 转码失败: ${error.message}`);
    return null;
  }
}

/**
 * 合并子节音频为 level=1 章节音频（通过 AudioMerger）
 */
async function mergeChapterAudio(chapterId: number): Promise<{ url: string; duration: number } | null> {
  try {
    // 使用 player.service 的 mergeChapterAudios 函数
    const { mergeChapterAudios } = await import('../src/modules/player/player.service');
    const mergedUrl = await mergeChapterAudios(chapterId);
    
    if (!mergedUrl) {
      console.log(`  ⚠️ mergeChapterAudios 返回 null，可能没有子节音频`);
      return null;
    }

    // 获取合并后时长
    let duration = 0;
    const localPath = resolveAudioUrlToLocal(mergedUrl);
    if (localPath) {
      duration = await getLocalAudioDuration(localPath);
    } else if (mergedUrl.startsWith('http')) {
      duration = await getRemoteAudioDuration(mergedUrl);
    }

    // 更新数据库时长
    if (duration > 0) {
      await prisma.bookChapter.update({
        where: { id: chapterId },
        data: { audioDuration: duration },
      });
    }

    console.log(`  ✅ 章节合并完成: ${mergedUrl.substring(0, 60)}..., 时长: ${duration}s`);
    return { url: mergedUrl, duration };
  } catch (error: any) {
    console.error(`  ❌ 章节合并失败: ${error.message}`);
    return null;
  }
}

// ============ 扫描函数 ============

async function scanLeafChapterIssues(): Promise<AudioIssue[]> {
  const issues: AudioIssue[] = [];

  const whereClause: any = {
    audioUrl: { not: '' },
  };
  if (chapterId) {
    whereClause.id = chapterId;
  } else if (bookId) {
    whereClause.bookId = bookId;
  }

  const chapters = await prisma.bookChapter.findMany({
    where: whereClause,
    include: { book: { select: { id: true, title: true } } },
    orderBy: [{ bookId: 'asc' }, { number: 'asc' }],
  });

  console.log(`📚 找到 ${chapters.length} 个有音频的章节（叶节点检查）\n`);

  // 预扫描 uploads 目录中的分段文件
  let allSegmentDirs: Map<string, string[]> | null = null;

  let currentBookId = -1;

  for (const chapter of chapters) {
    if (chapter.bookId !== currentBookId) {
      currentBookId = chapter.bookId;
      console.log(`📖 书籍 ${currentBookId}: ${chapter.book?.title || '未知'}`);
    }

    const wordCount = chapter.wordCount || chapter.content?.length || 0;
    const duration = chapter.audioDuration || 0;
    const rate = duration > 0 ? wordCount / duration : 0;

    let severity: IssueSeverity | null = null;
    let description = '';

    // 检查1: 语速异常
    if (duration > 0 && rate > SEVERE_RATE) {
      severity = 'critical';
      description = `严重异常：语速 ${rate.toFixed(1)}字/秒，音频时长仅${duration}s，应有约${Math.round(wordCount / CHARS_PER_SECOND)}s`;
    } else if (duration > 0 && rate > MILD_RATE) {
      severity = 'mild';
      description = `轻微异常：语速 ${rate.toFixed(1)}字/秒，偏快，可能丢失部分音频`;
    }

    // 检查2: 时长缺失
    if (duration === 0 && chapter.audioUrl) {
      severity = 'missing_duration';
      description = `音频时长为0，但audioUrl存在`;
    }

    // 检查3: 格式不匹配
    const localPath = resolveAudioUrlToLocal(chapter.audioUrl);
    if (localPath && chapter.audioUrl.endsWith('.mp3') && isWavFile(localPath)) {
      if (!severity) {
        severity = 'format_mismatch';
        description = `文件格式不匹配：扩展名.mp3但内容为WAV格式`;
      } else {
        description += '；且文件格式不匹配(.mp3扩展名但WAV内容)';
      }
    }

    if (severity) {
      const issue: AudioIssue = {
        chapterId: chapter.id,
        bookId: chapter.bookId,
        bookTitle: chapter.book?.title || '',
        chapterTitle: chapter.title,
        level: chapter.level,
        wordCount,
        audioDuration: duration,
        audioUrl: chapter.audioUrl,
        rate,
        severity,
        description,
        expectedDuration: Math.round(wordCount / CHARS_PER_SECOND),
      };

      // 查找本地分段文件
      let audioDir = inferAudioDir(chapter.audioUrl);
      
      // 如果从 URL 无法推断目录（远程 OSS URL），尝试扫描 uploads 目录
      // ⚠️ 注意：模糊匹配可能不准确，仅用于提示，不自动修复
      let fuzzyMatchedDir: string | null = null;
      if (!audioDir) {
        if (!allSegmentDirs) {
          console.log('  🔍 扫描本地 uploads 目录查找分段文件...');
          allSegmentDirs = scanAllSegmentDirs();
          console.log(`  📂 找到 ${allSegmentDirs.size} 个含分段文件的目录`);
        }
        
        // 通过字数匹配找到可能的目录（仅提示，不自动使用）
        if (allSegmentDirs.size > 0) {
          const match = findBestMatchingSegmentDir(allSegmentDirs, wordCount, duration);
          if (match && match.totalDuration > duration * 1.5) {
            fuzzyMatchedDir = match.dir;
            console.log(`  ⚠️ 章节 ${chapter.id} 可能有本地分段目录: ${fuzzyMatchedDir} (估算时长${Math.round(match.totalDuration)}s) - 需人工确认`);
          }
        }
      }
      
      if (audioDir) {
        const segments = findLocalSegments(audioDir);
        if (segments.length > 0) {
          issue.localSegments = segments;
          let totalSegDuration = 0;
          for (const seg of segments) {
            totalSegDuration += await getLocalAudioDuration(seg);
          }
          issue.segmentTotalDuration = totalSegDuration;
        }
      }

      // 保存模糊匹配结果
      if (fuzzyMatchedDir) {
        issue.fuzzyMatchedDir = fuzzyMatchedDir;
      }

      issues.push(issue);

      const icon = severity === 'critical' ? '🔴' : severity === 'mild' ? '🟡' : severity === 'format_mismatch' ? '🟠' : '🔵';
      console.log(`  ${icon} 章节 ${chapter.id} "${chapter.title}" (level=${chapter.level}): ${description}`);
      if (issue.localSegments?.length) {
        console.log(`     📎 找到 ${issue.localSegments.length} 个分段文件，总时长 ${issue.segmentTotalDuration}s`);
      }
      if (issue.fuzzyMatchedDir) {
        console.log(`     ⚠️ 疑似分段目录: ${issue.fuzzyMatchedDir}（需人工确认）`);
      }
    }
  }

  return issues;
}

async function scanMergedChapterIssues(): Promise<AudioIssue[]> {
  const issues: AudioIssue[] = [];

  // 查找所有 level=1 的章节（有子节）
  const whereClause: any = { level: 1 };
  if (chapterId) {
    whereClause.id = chapterId;
  } else if (bookId) {
    whereClause.bookId = bookId;
  }

  const level1Chapters = await prisma.bookChapter.findMany({
    where: whereClause,
    include: { book: { select: { id: true, title: true } } },
    orderBy: [{ bookId: 'asc' }, { number: 'asc' }],
  });

  console.log(`\n📚 找到 ${level1Chapters.length} 个一级章节（合并音频检查）\n`);

  let currentBookId = -1;

  for (const chapter of level1Chapters) {
    if (chapter.bookId !== currentBookId) {
      currentBookId = chapter.bookId;
      console.log(`📖 书籍 ${currentBookId}: ${chapter.book?.title || '未知'}`);
    }

    // 获取子节音频
    const children = await prisma.bookChapter.findMany({
      where: { parentId: chapter.id, audioUrl: { not: '' } },
      orderBy: { number: 'asc' },
    });

    if (children.length === 0) continue;

    const childrenTotalDuration = children.reduce((sum, c) => sum + (c.audioDuration || 0), 0);
    const chapterDuration = chapter.audioDuration || 0;

    // 检查: 合并音频时长应接近子节时长总和
    if (chapter.audioUrl && childrenTotalDuration > 0) {
      const ratio = chapterDuration / childrenTotalDuration;
      if (ratio < MERGE_TOLERANCE) {
        const issue: AudioIssue = {
          chapterId: chapter.id,
          bookId: chapter.bookId,
          bookTitle: chapter.book?.title || '',
          chapterTitle: chapter.title,
          level: chapter.level,
          wordCount: chapter.wordCount || 0,
          audioDuration: chapterDuration,
          audioUrl: chapter.audioUrl,
          rate: 0,
          severity: 'merge_incomplete',
          description: `合并不完整：章音频${chapterDuration}s < 子节总时长${childrenTotalDuration}s（比例${(ratio * 100).toFixed(0)}%）`,
          expectedDuration: childrenTotalDuration,
        };
        issues.push(issue);
        console.log(`  🟣 章节 ${chapter.id} "${chapter.title}": ${issue.description}`);
      } else {
        // 正常
        process.stdout.write(`  ✅ 章节 ${chapter.id} "${chapter.title}": 合并正常 (${chapterDuration}s ≈ ${childrenTotalDuration}s)\n`);
      }
    } else if (!chapter.audioUrl && children.length > 0) {
      // 章没有合并音频但有子节
      console.log(`  ⚪ 章节 ${chapter.id} "${chapter.title}": 无合并音频（${children.length}个子节总时长${childrenTotalDuration}s）`);
    }
  }

  return issues;
}

// ============ 修复函数 ============

async function fixIssue(issue: AudioIssue): Promise<boolean> {
  console.log(`\n--- 修复章节 ${issue.chapterId} "${issue.chapterTitle}" ---`);
  console.log(`  问题: ${issue.description}`);

  let result: { url: string; duration: number } | null = null;

  switch (issue.severity) {
    case 'critical':
    case 'mild':
      // 策略1: 本地分段文件重新合并
      if (issue.localSegments && issue.localSegments.length > 1) {
        console.log(`  策略: 重新合并 ${issue.localSegments.length} 个分段文件...`);
        const audioDir = path.dirname(issue.localSegments[0]);
        result = await mergeAndUpload(issue.localSegments, audioDir, issue.chapterId);
      }
      // 策略2: 单个分段文件（可能格式不匹配）
      else if (issue.localSegments && issue.localSegments.length === 1) {
        const localFile = issue.localSegments[0];
        if (isWavFile(localFile)) {
          console.log(`  策略: WAV 分段转码为 MP3...`);
          result = await transcodeAndUpload(localFile, issue.chapterId);
        } else {
          // 尝试重新检测时长
          const actualDuration = await getLocalAudioDuration(localFile);
          if (actualDuration > issue.audioDuration * 1.5) {
            console.log(`  策略: 本地文件实际时长(${actualDuration}s) > 数据库时长(${issue.audioDuration}s)，更新`);
            result = { url: issue.audioUrl, duration: actualDuration };
          }
        }
      }
      // 策略3: 远程文件
      else if (issue.audioUrl.startsWith('http')) {
        const remoteDuration = await getRemoteAudioDuration(issue.audioUrl);
        if (remoteDuration > issue.audioDuration * 1.5) {
          console.log(`  策略: 远程时长(${remoteDuration}s) > 数据库时长(${issue.audioDuration}s)，更新数据库`);
          result = { url: issue.audioUrl, duration: remoteDuration };
        } else {
          console.log(`  ⚠️ 远程音频时长也异常，无法自动修复（需重新生成音频）`);
        }
      } else {
        console.log(`  ⚠️ 没有本地分段文件，无法自动修复（需重新生成音频）`);
      }
      break;

    case 'format_mismatch':
      const localFile = resolveAudioUrlToLocal(issue.audioUrl);
      if (localFile) {
        console.log(`  策略: 转码 WAV→MP3...`);
        result = await transcodeAndUpload(localFile, issue.chapterId);
      }
      break;

    case 'missing_duration':
      const localPath = resolveAudioUrlToLocal(issue.audioUrl);
      if (localPath) {
        const duration = await getLocalAudioDuration(localPath);
        if (duration > 0) {
          console.log(`  策略: 补充本地音频时长 ${duration}s`);
          result = { url: issue.audioUrl, duration };
        }
      } else if (issue.audioUrl.startsWith('http')) {
        const duration = await getRemoteAudioDuration(issue.audioUrl);
        if (duration > 0) {
          console.log(`  策略: 补充远程音频时长 ${duration}s`);
          result = { url: issue.audioUrl, duration };
        }
      }
      break;

    case 'merge_incomplete':
      console.log(`  策略: 重新合并子节音频...`);
      result = await mergeChapterAudio(issue.chapterId);
      break;
  }

  // 更新数据库
  if (result) {
    try {
      await prisma.bookChapter.update({
        where: { id: issue.chapterId },
        data: {
          audioUrl: result.url,
          audioDuration: result.duration,
        },
      });
      const newRate = result.duration > 0 ? (issue.wordCount / result.duration).toFixed(1) : 'N/A';
      console.log(`  ✅ 数据库已更新: duration=${result.duration}s, 语速=${newRate}字/秒`);
      return true;
    } catch (error: any) {
      console.error(`  ❌ 数据库更新失败: ${error.message}`);
      return false;
    }
  }

  console.log(`  ⏭️ 无法自动修复，跳过`);
  return false;
}

// ============ 主流程 ============

async function main() {
  console.log('='.repeat(70));
  console.log('🔍 音频问题扫描与修复工具');
  console.log('='.repeat(70));
  console.log(`模式: ${mergeAll ? '🔄 重新合并所有' : shouldFix ? '🔧 扫描+修复' : '🔍 仅扫描'}`);
  if (bookId) console.log(`指定书籍 ID: ${bookId}`);
  if (chapterId) console.log(`指定章节 ID: ${chapterId}`);
  console.log('');

  // --merge-all 模式：重新合并所有 level=1 章节
  if (mergeAll) {
    console.log('🔄 重新合并所有 level=1 章节音频...\n');

    const whereClause: any = { level: 1 };
    if (bookId) whereClause.bookId = bookId;

    const chapters = await prisma.bookChapter.findMany({
      where: whereClause,
      include: { book: { select: { id: true, title: true } } },
      orderBy: [{ bookId: 'asc' }, { number: 'asc' }],
    });

    let successCount = 0;
    let failCount = 0;

    for (const chapter of chapters) {
      console.log(`\n📖 合并章节 ${chapter.id} "${chapter.title}"...`);
      try {
        const result = await mergeChapterAudio(chapter.id);
        if (result) {
          successCount++;
        } else {
          console.log(`  ⚠️ 无子节音频可合并，跳过`);
          failCount++;
        }
      } catch (error: any) {
        console.error(`  ❌ 合并失败: ${error.message}`);
        failCount++;
      }
    }

    console.log(`\n${'='.repeat(70)}`);
    console.log(`📊 合并结果: ✅ ${successCount} 成功, ❌ ${failCount} 失败`);
    await prisma.$disconnect();
    return;
  }

  // 普通扫描模式
  const leafIssues = await scanLeafChapterIssues();
  const mergeIssues = await scanMergedChapterIssues();
  const allIssues = [...leafIssues, ...mergeIssues];

  // 汇总
  console.log('\n' + '='.repeat(70));
  console.log('📊 扫描结果汇总');
  console.log('='.repeat(70));

  const critical = allIssues.filter(i => i.severity === 'critical');
  const mild = allIssues.filter(i => i.severity === 'mild');
  const formatMismatch = allIssues.filter(i => i.severity === 'format_mismatch');
  const missingDur = allIssues.filter(i => i.severity === 'missing_duration');
  const mergeIncomplete = allIssues.filter(i => i.severity === 'merge_incomplete');

  console.log(`🔴 严重异常（语速>${SEVERE_RATE}字/秒）: ${critical.length} 个`);
  console.log(`🟡 轻微异常（语速${MILD_RATE}-${SEVERE_RATE}字/秒）: ${mild.length} 个`);
  console.log(`🟠 格式不匹配（WAV存为.mp3）: ${formatMismatch.length} 个`);
  console.log(`🔵 时长缺失: ${missingDur.length} 个`);
  console.log(`🟣 合并不完整（章音频<子节总时长80%）: ${mergeIncomplete.length} 个`);
  console.log(`总计: ${allIssues.length} 个问题\n`);

  if (allIssues.length === 0) {
    console.log('✅ 没有发现音频问题！');
    await prisma.$disconnect();
    return;
  }

  // 输出详细信息
  if (allIssues.length > 0) {
    console.log('📋 问题详情:');
    for (const issue of allIssues) {
      const icon = issue.severity === 'critical' ? '🔴' :
                   issue.severity === 'mild' ? '🟡' :
                   issue.severity === 'format_mismatch' ? '🟠' :
                   issue.severity === 'merge_incomplete' ? '🟣' : '🔵';
      console.log(`  ${icon} [${issue.severity}] 章节${issue.chapterId} "${issue.chapterTitle}" (book=${issue.bookId}, level=${issue.level})`);
      console.log(`     ${issue.description}`);
      if (issue.localSegments?.length) {
        console.log(`     分段文件: ${issue.localSegments.length}个, 分段总时长: ${issue.segmentTotalDuration}s`);
      }
      if (issue.expectedDuration) {
        console.log(`     预期时长: ~${issue.expectedDuration}s, 实际: ${issue.audioDuration}s`);
      }
      if (issue.fuzzyMatchedDir) {
        console.log(`     ⚠️ 疑似分段目录: ${issue.fuzzyMatchedDir}（需人工确认后再修复）`);
      }
    }
  }

  if (!shouldFix) {
    console.log('\n💡 使用 --fix 参数自动修复问题:');
    console.log('   npx tsx scripts/fix-audio-issues.ts --fix');
    console.log('   npx tsx scripts/fix-audio-issues.ts --fix --chapter 11');
    console.log('\n💡 使用 --merge-all 重新合并所有 level=1 章节音频:');
    console.log('   npx tsx scripts/fix-audio-issues.ts --merge-all');
    console.log('   npx tsx scripts/fix-audio-issues.ts --merge-all --book 5');
    await prisma.$disconnect();
    return;
  }

  // 自动修复
  console.log('\n' + '='.repeat(70));
  console.log('🔧 开始自动修复');
  console.log('='.repeat(70) + '\n');

  let fixedCount = 0;
  let failedCount = 0;

  for (const issue of allIssues) {
    const success = await fixIssue(issue);
    if (success) fixedCount++;
    else failedCount++;
  }

  console.log('\n' + '='.repeat(70));
  console.log('📊 修复结果');
  console.log('='.repeat(70));
  console.log(`✅ 修复成功: ${fixedCount} 个`);
  console.log(`❌ 修复失败/跳过: ${failedCount} 个`);

  await prisma.$disconnect();
}

main().catch(err => {
  console.error('脚本执行失败:', err);
  process.exit(1);
});
