#!/usr/bin/env node
/**
 * 批量视频导出脚本 - JavaScript版本
 * 用于批量处理项目ID，生成剪辑计划并调用导出接口
 * 
 * 使用方法:
 * node scripts/batch-export.js --projects "project1,project2,project3"
 * 或者
 * node scripts/batch-export.js --file projects.txt
 */

const fs = require('fs');
const path = require('path');
const https = require('https');
const http = require('http');

/**
 * 批量视频导出处理器
 */
class BatchVideoExporter {
  constructor(config) {
    this.config = config;
    this.projectStatuses = new Map();
    this.logFile = path.join(config.outputDir, `batch-export-${Date.now()}.log`);
    
    // 确保输出目录存在
    if (!fs.existsSync(config.outputDir)) {
      fs.mkdirSync(config.outputDir, { recursive: true });
    }
  }

  /** 记录日志 */
  log(message, level = 'info') {
    const timestamp = new Date().toISOString();
    const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
    
    console.log(logMessage);
    fs.appendFileSync(this.logFile, logMessage + '\n');
  }

  /** HTTP请求封装 */
  async makeRequest(endpoint, data = null, method = 'POST') {
    return new Promise((resolve, reject) => {
      const url = new URL(`${this.config.apiBaseUrl}${endpoint}`);
      const isHttps = url.protocol === 'https:';
      const httpModule = isHttps ? https : http;
      
      const options = {
        hostname: url.hostname,
        port: url.port || (isHttps ? 443 : 80),
        path: url.pathname + url.search,
        method: method,
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${this.config.token}`,
          'X-EASE-ADMIN-TOKEN': this.config.token,
        }
      };

      const req = httpModule.request(options, (res) => {
        let responseData = '';
        
        res.on('data', (chunk) => {
          responseData += chunk;
        });
        
        res.on('end', () => {
          if (res.statusCode >= 200 && res.statusCode < 300) {
            try {
              resolve(JSON.parse(responseData));
            } catch (e) {
              resolve(responseData);
            }
          } else {
            reject(new Error(`HTTP ${res.statusCode}: ${responseData}`));
          }
        });
      });

      req.on('error', (error) => {
        reject(error);
      });

      if (data && method === 'POST') {
        req.write(JSON.stringify(data));
      }
      
      req.end();
    });
  }

  /** 生成剪辑计划 */
  async generateEditPlan(projectId) {
    this.log(`开始为项目 ${projectId} 生成剪辑计划...`);
    const maxAttempts = 3; // 最多重试3次
    const retryDelayMs = Number(this.config.retryDelay || 3000);

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        this.log(`项目 ${projectId}: 第${attempt}次尝试获取剪辑计划...`);

        const response = await this.makeRequest('/edit-plan/generate-by-project', {
          project_id: projectId
        });

        if (response.code === 0 && response.data && response.data.success && response.data.editing_plan) {
          this.log(`项目 ${projectId}: 剪辑计划生成成功`);
          return response.data.editing_plan;
        }

        const errorMsg = response && (response.message || response.msg) ? (response.message || response.msg) : '未知错误';
        throw new Error(`剪辑计划生成失败: ${errorMsg}`);
      } catch (error) {
        if (attempt < maxAttempts) {
          this.log(`项目 ${projectId}: 获取剪辑计划失败（第${attempt}次）- ${error.message}，${Math.round(retryDelayMs/1000)}秒后重试...`, 'warn');
          await new Promise(resolve => setTimeout(resolve, retryDelayMs));
        } else {
          // 第3次仍失败，直接抛出，终止该项目后续导出
          throw new Error(`获取剪辑计划失败，已重试${maxAttempts}次: ${error.message}`);
        }
      }
    }

    // 理论上不会到达这里
    throw new Error(`获取剪辑计划失败`);
  }

  /** 解析时间码为毫秒 */
  parseTimecodeToMs(timecode) {
    const parts = timecode.split(':');
    if (parts.length !== 3) return 0;
    
    const hours = parseInt(parts[0]) || 0;
    const minutes = parseInt(parts[1]) || 0;
    const secondsParts = parts[2].split('.');
    const seconds = parseInt(secondsParts[0]) || 0;
    const milliseconds = parseInt((secondsParts[1] || '').padEnd(3, '0').slice(0, 3)) || 0;
    
    return Math.round((hours * 3600 + minutes * 60 + seconds) * 1000 + milliseconds);
  }

  /** 构建导出请求数据 */
  buildExportRequest(projectId, editingPlan) {
    this.log(`项目 ${projectId}: 构建导出请求数据...`);
    
    const defaultClipDuration = 8000; // 8秒
    let currentStartTime = 0;
    const videoElements = [];
    
    // 处理剪辑计划中的时间线信息
    if (editingPlan.editing_sequence_plans && editingPlan.editing_sequence_plans.length > 0) {
      const timelineClips = editingPlan.editing_sequence_plans[0].timeline_clips || [];
      this.log(`项目 ${projectId}: 使用剪辑计划中的 ${timelineClips.length} 个时间线片段`);
      
      timelineClips.forEach((clip, index) => {
        const sequenceStartMs = this.parseTimecodeToMs(clip.sequence_start_timecode || "00:00:00.000");
        const sourceInMs = this.parseTimecodeToMs(clip.source_in_timecode || "00:00:00.000");
        const sourceOutMs = this.parseTimecodeToMs(clip.source_out_timecode || "00:00:08.000");
        const clipDurationMs = this.parseTimecodeToMs(clip.clip_duration_in_sequence || "00:00:08.000");
        
        const element = {
          id: clip.sequence_clip_id || `video_${index + 1}`,
          src: clip.video_url,
          start: currentStartTime,
          end: currentStartTime + clipDurationMs,
          in: sourceInMs,
          out: sourceOutMs,
          _source_type: (clip.video_url && clip.video_url.startsWith('http')) ? 'remote_url' : 'local'
        };
        
        videoElements.push(element);
        currentStartTime += clipDurationMs;
      });
    }
    
    const totalDuration = currentStartTime || defaultClipDuration;
    
    // 处理字幕
    const texts = [];
    if (editingPlan.finalized_dialogue_track && editingPlan.finalized_dialogue_track.final_dialogue_segments) {
      editingPlan.finalized_dialogue_track.final_dialogue_segments.forEach((dialogue, index) => {
        texts.push({
          id: dialogue.sequence_clip_id || `text_${index + 1}`,
          text: dialogue.transcript,
          start: this.parseTimecodeToMs(dialogue.start_timecode || "00:00:00.000"),
          end: this.parseTimecodeToMs(dialogue.end_timecode || "00:00:02.000"),
          style: {
            fontFamily: 'Arial',
            fontSize: 40,
            color: '#FFFFFF',
            backgroundColor: 'transparent',
            fontWeight: 'normal',
            fontStyle: 'normal',
            align: 'center',
            shadow: true
          }
        });
      });
    }
    
    // 构建导出请求
    const exportRequest = {
      project_id: projectId,
      ir: {
        width: 1920,
        height: 1080,
        fps: 30,
        duration: totalDuration,
        video: videoElements,
        texts: texts,
        audio: [],
        transitions: []
      },
      options: {
        quality: this.config.exportQuality,
        codec: 'h264',
        subtitleMode: 'hard'
      }
    };
    
    this.log(`项目 ${projectId}: 导出请求数据构建完成，视频片段: ${videoElements.length}, 字幕片段: ${texts.length}`);
    return exportRequest;
  }

  /** 调用导出流接口 */
  async callExportStream(exportRequest, attemptNumber = 1) {
    const projectId = exportRequest.project_id;
    this.log(`项目 ${projectId}: 开始调用导出流接口（第${attemptNumber}次尝试）...`);
    
    // 使用fetch API（Node.js 18+支持）
    let fetch;
    try {
      fetch = globalThis.fetch;
    } catch {
      // 如果没有fetch，使用node-fetch或提示升级Node.js
      throw new Error('需要Node.js 18+或安装node-fetch包');
    }
    
    const response = await fetch(`${this.config.exportApiBaseUrl}/api/export/stream`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'text/event-stream',
        'Authorization': `Bearer ${this.config.token}`,
        'X-EASE-ADMIN-TOKEN': this.config.token,
      },
      body: JSON.stringify(exportRequest)
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`导出接口错误: ${response.status} ${errorText}`);
    }

    this.log(`项目 ${projectId}: 导出接口调用成功，开始处理SSE流...`);

    // 处理SSE流式响应
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let finalResult = null;
    let detectedTaskId = null;

    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value);
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            try {
              const eventData = JSON.parse(line.slice(6));
              
              // 提取任务ID
              if (eventData.export_id || eventData.task_id) {
                detectedTaskId = eventData.export_id || eventData.task_id;
              }

              // 处理不同类型的事件
              switch (eventData.type) {
                case 'start':
                  this.log(`项目 ${projectId}: 导出开始 - ${eventData.message}`);
                  if (eventData.export_id || eventData.task_id) {
                    detectedTaskId = eventData.export_id || eventData.task_id;
                  }
                  break;
                
                case 'progress':
                  const progressPercent = Math.round((eventData.progress || 0) * 100);
                  this.log(`项目 ${projectId}: 导出进度 ${progressPercent}% - ${eventData.stage || 'processing'} - ${eventData.message}`);
                  break;
                
                case 'complete':
                  this.log(`项目 ${projectId}: 导出完成！`);
                  finalResult = eventData;
                  if (detectedTaskId && !finalResult.export_id && !finalResult.task_id) {
                    finalResult.export_id = detectedTaskId;
                  }
                  return finalResult;
                
                case 'error':
                  throw new Error(`导出失败: ${eventData.message}`);
              }
            } catch (parseError) {
              this.log(`项目 ${projectId}: 解析SSE事件失败: ${line}`, 'warn');
            }
          }
        }
      }
    } finally {
      reader.releaseLock();
    }

    return finalResult;
  }

  /** 轮询导出进度 */
  async pollExportProgress(taskId, projectId) {
    this.log(`项目 ${projectId}: 开始轮询导出进度，任务ID: ${taskId}`);
    const maxAttempts = 120; // 最多轮询10分钟
    let attempts = 0;

    while (attempts < maxAttempts) {
      try {
        // 使用导出API基础URL进行轮询
        const progressUrl = `${this.config.exportApiBaseUrl}/api/export/task/${taskId}/progress`;
        const progressResponse = await fetch(progressUrl, {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${this.config.token}`,
            'X-EASE-ADMIN-TOKEN': this.config.token,
          }
        });
        
        if (!progressResponse.ok) {
          throw new Error(`进度查询失败: ${progressResponse.status} ${progressResponse.statusText}`);
        }
        
        const response = await progressResponse.json();
        const { status, progress } = response;
        
        if (status === 'completed') {
          this.log(`项目 ${projectId}: 导出任务完成！视频URL: ${progress && progress.video_url}`);
          return {
            task_id: taskId,
            status: status,
            video_url: progress && progress.video_url,
            file_size: progress && progress.file_size,
            export_id: progress && progress.export_id
          };
        } else if (status === 'failed') {
          const errorMessage = `导出任务失败: ${(progress && progress.message) || '未知错误'}`;
          throw new Error(errorMessage);
        } else if (status === 'error') {
          const errorMessage = `导出任务错误: ${(progress && progress.message) || '未知错误'}`;
          throw new Error(errorMessage);
        } else {
          const percentage = (progress && progress.percentage) || 0;
          const message = (progress && progress.message) || '处理中...';
          this.log(`项目 ${projectId}: 导出进度 ${percentage}% - ${message}`);
        }
        
        attempts++;
        await new Promise(resolve => setTimeout(resolve, 5000)); // 5秒间隔
      } catch (error) {
        this.log(`项目 ${projectId}: 轮询进度出错: ${error.message}`, 'error');
        // 对于明确的失败/错误状态，立即抛出，让上层进行重新导出重试
        if (
          typeof error?.message === 'string' &&
          (error.message.includes('导出任务失败') || error.message.includes('导出任务错误'))
        ) {
          throw error;
        }
        // 其他网络类错误，继续有限次数的轮询重试
        attempts++;
        if (attempts >= maxAttempts) {
          throw error;
        }
        await new Promise(resolve => setTimeout(resolve, 5000));
      }
    }

    throw new Error(`轮询超时，已尝试${maxAttempts}次`);
  }

  /** 导出任务处理（包含重试逻辑） */
  async processExportWithRetry(exportRequest) {
    const projectId = exportRequest.project_id;
    const maxExportRetries = 3; // 导出重试3次
    
    for (let attempt = 1; attempt <= maxExportRetries; attempt++) {
      try {
        this.log(`项目 ${projectId}: 开始第${attempt}次导出尝试...`);
        
        // 1. 调用导出流接口
        let exportResult = await this.callExportStream(exportRequest, attempt);
        
        // 2. 如果SSE没有返回完整结果，使用轮询
        let taskId = null;
        if (!exportResult || !exportResult.video_url) {
          taskId = (exportResult && exportResult.export_id) || (exportResult && exportResult.task_id);
          if (!taskId) {
            throw new Error('无法获取任务ID，无法轮询进度');
          }
          
          try {
            exportResult = await this.pollExportProgress(taskId, projectId);
          } catch (pollError) {
            // 如果轮询过程中发现任务失败，并且还有重试机会，则重新导出
            if (pollError.message.includes('导出任务失败') || pollError.message.includes('导出任务错误')) {
              this.log(`项目 ${projectId}: 第${attempt}次导出失败 - ${pollError.message}`, 'warn');
              
              if (attempt < maxExportRetries) {
                this.log(`项目 ${projectId}: 准备重新导出（剩余${maxExportRetries - attempt}次重试机会）...`);
                await new Promise(resolve => setTimeout(resolve, 3000)); // 等待3秒后重试
                continue;
              } else {
                throw new Error(`导出失败，已重试${maxExportRetries}次: ${pollError.message}`);
              }
            } else {
              // 其他错误（如网络错误、超时等）直接抛出
              throw pollError;
            }
          }
        }
        
        // 3. 导出成功，返回结果
        this.log(`项目 ${projectId}: 第${attempt}次导出尝试成功！`);
        return exportResult;
        
      } catch (error) {
        // 如果是导出接口调用失败（如网络错误、服务器错误等）
        this.log(`项目 ${projectId}: 第${attempt}次导出尝试失败 - ${error.message}`, 'warn');
        
        if (attempt < maxExportRetries) {
          this.log(`项目 ${projectId}: 准备重新导出（剩余${maxExportRetries - attempt}次重试机会）...`);
          await new Promise(resolve => setTimeout(resolve, 3000)); // 等待3秒后重试
        } else {
          throw new Error(`导出失败，已重试${maxExportRetries}次: ${error.message}`);
        }
      }
    }
    
    throw new Error(`导出失败，已达到最大重试次数${maxExportRetries}`);
  }

  /** 处理单个项目 */
  async processProject(projectId) {
    const status = {
      projectId,
      status: 'pending',
      editPlanGenerated: false,
      exportStarted: false,
      exportCompleted: false,
      attempts: 0,
      startTime: Date.now()
    };
    
    this.projectStatuses.set(projectId, status);
    
    try {
      // 1. 生成剪辑计划
      status.status = 'generating_plan';
      this.log(`项目 ${projectId}: 开始处理...`);
      
      const editingPlan = await this.generateEditPlan(projectId);
      status.editPlanGenerated = true;
      this.log(`项目 ${projectId}: 剪辑计划生成完成`);
      
      // 2. 构建导出请求
      const exportRequest = this.buildExportRequest(projectId, editingPlan);
      
      // 3. 调用导出接口（包含重试逻辑）
      status.status = 'exporting';
      status.exportStarted = true;
      
      const exportResult = await this.processExportWithRetry(exportRequest);
      
      // 4. 处理完成
      status.status = 'completed';
      status.exportCompleted = true;
      status.videoUrl = exportResult.video_url;
      status.endTime = Date.now();
      
      this.log(`项目 ${projectId}: 处理完成！视频URL: ${exportResult.video_url}`);
      this.log(`项目 ${projectId}: 总耗时: ${((status.endTime - status.startTime) / 1000).toFixed(2)}秒`);
      
    } catch (error) {
      status.status = 'failed';
      status.error = error.message;
      status.endTime = Date.now();
      
      this.log(`项目 ${projectId}: 处理失败: ${status.error}`, 'error');
      throw error;
    }
  }

  /** 批量处理项目 */
  async processProjects(projectIds) {
    this.log(`开始批量处理 ${projectIds.length} 个项目...`);
    this.log(`配置: 并发数=${this.config.concurrency}, 最大重试=${this.config.maxRetries}`);
    
    const results = {
      total: projectIds.length,
      completed: 0,
      failed: 0,
      errors: []
    };
    
    // 分批并发处理
    for (let i = 0; i < projectIds.length; i += this.config.concurrency) {
      const batch = projectIds.slice(i, i + this.config.concurrency);
      this.log(`处理第 ${Math.floor(i/this.config.concurrency) + 1} 批，项目: ${batch.join(', ')}`);
      
      const promises = batch.map(async (projectId) => {
        let attempts = 0;
        
        while (attempts < this.config.maxRetries) {
          try {
            await this.processProject(projectId);
            results.completed++;
            break;
          } catch (error) {
            attempts++;
            const errorMsg = error.message;
            
            if (attempts >= this.config.maxRetries) {
              this.log(`项目 ${projectId}: 达到最大重试次数，放弃处理`, 'error');
              results.failed++;
              results.errors.push({ projectId, error: errorMsg });
            } else {
              this.log(`项目 ${projectId}: 第${attempts}次尝试失败，${this.config.retryDelay/1000}秒后重试...`, 'warn');
              await new Promise(resolve => setTimeout(resolve, this.config.retryDelay));
            }
          }
        }
      });
      
      await Promise.all(promises);
    }
    
    // 生成处理报告
    this.generateReport(results);
  }

  /** 生成处理报告 */
  generateReport(results) {
    const reportPath = path.join(this.config.outputDir, `batch-report-${Date.now()}.json`);
    
    const report = {
      timestamp: new Date().toISOString(),
      config: {
        concurrency: this.config.concurrency,
        maxRetries: this.config.maxRetries,
        exportQuality: this.config.exportQuality
      },
      results: results,
      projects: Array.from(this.projectStatuses.values()).map(status => ({
        projectId: status.projectId,
        status: status.status,
        videoUrl: status.videoUrl,
        error: status.error,
        duration: status.endTime && status.startTime ? 
          ((status.endTime - status.startTime) / 1000) : null
      }))
    };
    
    fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
    
    this.log(`\n=== 批量处理完成 ===`);
    this.log(`总项目数: ${results.total}`);
    this.log(`成功: ${results.completed}`);
    this.log(`失败: ${results.failed}`);
    this.log(`处理报告: ${reportPath}`);
    this.log(`详细日志: ${this.logFile}`);
    
    if (results.errors.length > 0) {
      this.log(`\n失败项目:`);
      results.errors.forEach((error) => {
        this.log(`  - ${error.projectId}: ${error.error}`, 'error');
      });
    }
  }
}

/** 从命令行参数解析项目ID列表 */
function parseProjectIds() {
  const args = process.argv.slice(2);
  const projectIds = [];
  
  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--projects' && i + 1 < args.length) {
      // 从命令行参数解析
      projectIds.push(...args[i + 1].split(',').map(id => id.trim()).filter(Boolean));
    } else if (args[i] === '--file' && i + 1 < args.length) {
      // 从文件读取
      const filePath = args[i + 1];
      if (fs.existsSync(filePath)) {
        const content = fs.readFileSync(filePath, 'utf-8');
        projectIds.push(...content.split('\n').map(id => id.trim()).filter(id => id && !id.startsWith('#')));
      } else {
        console.error(`文件不存在: ${filePath}`);
        process.exit(1);
      }
    }
  }
  
  if (projectIds.length === 0) {
    console.error('请提供项目ID列表:');
    console.error('  使用 --projects "id1,id2,id3"');
    console.error('  或者 --file projects.txt');
    process.exit(1);
  }
  
  return projectIds;
}

/** 主函数 */
async function main() {
  try {
    // 解析项目ID列表
    const projectIds = parseProjectIds();
    
    // 配置参数（可以通过环境变量或配置文件自定义）
    const config = {
      apiBaseUrl: process.env.API_BASE_URL || 'https://api.video.movieflow.ai',
      exportApiBaseUrl: process.env.EXPORT_API_BASE_URL || 'https://smartcut.api.movieflow.ai',
      token: process.env.AUTH_TOKEN || 'your-auth-token',
      userId: process.env.USER_ID || 'your-user-id',
      concurrency: parseInt(process.env.CONCURRENCY || '3'),
      maxRetries: parseInt(process.env.MAX_RETRIES || '3'),
      retryDelay: parseInt(process.env.RETRY_DELAY || '5000'),
      exportQuality: process.env.EXPORT_QUALITY || 'standard',
      outputDir: process.env.OUTPUT_DIR || './batch-export-output'
    };
    
    console.log(`开始批量处理 ${projectIds.length} 个项目...`);
    console.log(`项目列表: ${projectIds.join(', ')}`);
    
    // 创建处理器并执行
    const exporter = new BatchVideoExporter(config);
    await exporter.processProjects(projectIds);
    
  } catch (error) {
    console.error('批量处理失败:', error);
    process.exit(1);
  }
}

// 运行主函数
if (require.main === module) {
  main();
}

module.exports = { BatchVideoExporter };
