/**
 * FFmpeg 处理器（支持 OSS 和本地存储）
 * 自动处理远程 URL 的下载和上传
 */

import fs from 'fs';
import path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
import { v4 as uuidv4 } from 'uuid';
import axios from 'axios';
import { storageService } from './storage.service';

const execAsync = promisify(exec);

// 临时文件目录
const TEMP_DIR = path.join(process.cwd(), 'temp');

// 确保临时目录存在
if (!fs.existsSync(TEMP_DIR)) {
  fs.mkdirSync(TEMP_DIR, { recursive: true });
}

export class FFmpegProcessor {
  /**
   * 下载远程文件到本地临时目录
   * @param url 文件 URL（OSS URL 或本地路径）
   * @returns 本地临时文件路径
   */
  static async downloadFile(url: string): Promise<string> {
    // 如果是本地路径，直接返回
    if (url.startsWith('/uploads/') || url.startsWith('./')) {
      const fullPath = url.startsWith('./') ? url : path.join(process.cwd(), url);
      if (fs.existsSync(fullPath)) {
        return fullPath;
      }
    }

    // 下载远程文件
    const tempFile = path.join(TEMP_DIR, `${uuidv4()}_${path.basename(url)}`);
    
    console.log(`[FFmpeg] 下载文件: ${url}`);
    
    const response = await axios({
      method: 'GET',
      url: url.startsWith('http') ? url : `http://localhost:3000${url}`,
      responseType: 'stream',
      timeout: 60000, // 60秒超时
    });

    const writer = fs.createWriteStream(tempFile);
    response.data.pipe(writer);

    return new Promise((resolve, reject) => {
      writer.on('finish', () => {
        console.log(`[FFmpeg] 下载完成: ${tempFile}`);
        resolve(tempFile);
      });
      writer.on('error', reject);
    });
  }

  /**
   * 合并多个音频文件（支持 OSS URL）
   * @param inputUrls 输入文件 URL 数组
   * @param outputFormat 输出格式（mp3/wav）
   * @returns 输出文件 URL
   */
  static async mergeAudio(
    inputUrls: string[],
    outputFormat: string = 'mp3'
  ): Promise<string> {
    if (inputUrls.length === 0) {
      throw new Error('没有音频文件需要合并');
    }

    if (inputUrls.length === 1) {
      // 单个文件直接返回
      return inputUrls[0];
    }

    const tempFiles: string[] = [];
    const listFile = path.join(TEMP_DIR, `${uuidv4()}_list.txt`);
    const outputFile = path.join(TEMP_DIR, `${uuidv4()}_merged.${outputFormat}`);

    try {
      // 1. 下载所有文件到本地
      console.log(`[FFmpeg] 开始下载 ${inputUrls.length} 个音频文件...`);
      for (const url of inputUrls) {
        const localFile = await this.downloadFile(url);
        tempFiles.push(localFile);
      }

      // 2. 创建 FFmpeg 文件列表（路径中的反斜杠转为正斜杠，避免 Windows 下 FFmpeg 解析失败）
      const listContent = tempFiles.map(f => `file '${f.replace(/\\/g, '/')}'`).join('\n');
      fs.writeFileSync(listFile, listContent);
      
      console.log(`[FFmpeg] 开始合并音频...`);
      
      let cmd: string;
      if (outputFormat === 'mp3') {
        cmd = `ffmpeg -f concat -safe 0 -i "${listFile}" -c:a libmp3lame -b:a 192k -y "${outputFile}"`;
      } else {
        cmd = `ffmpeg -f concat -safe 0 -i "${listFile}" -c copy -y "${outputFile}"`;
      }

      await execAsync(cmd, { timeout: 300000 }); // 5分钟超时
      console.log(`[FFmpeg] 音频合并成功: ${outputFile}`);

      // 4. 上传到存储
      const audioId = uuidv4();
      const finalUrl = await storageService.uploadAudio(outputFile, audioId);
      console.log(`[FFmpeg] 上传完成: ${finalUrl}`);

      return finalUrl;
    } finally {
      // 5. 清理临时文件（下载的文件 + 列表文件 + 合并输出文件）
      this.cleanupTempFiles([...tempFiles, listFile, outputFile]);
    }
  }

  /**
   * 合并音频和视频（添加背景音乐）
   * @param audioUrl 主音频 URL
   * @param videoUrl 视频 URL（无音频）
   * @param bgmUrl 背景音乐 URL（可选）
   * @param bgmVolume 背景音乐音量（0-1，默认 0.3）
   * @returns 输出视频 URL
   */
  static async mergeAudioVideo(
    audioUrl: string,
    videoUrl: string,
    bgmUrl?: string,
    bgmVolume: number = 0.3
  ): Promise<string> {
    const tempFiles: string[] = [];
    const outputFile = path.join(TEMP_DIR, `${uuidv4()}_output.mp4`);

    try {
      // 1. 下载文件
      console.log(`[FFmpeg] 下载音频和视频文件...`);
      const audioFile = await this.downloadFile(audioUrl);
      const videoFile = await this.downloadFile(videoUrl);
      tempFiles.push(audioFile, videoFile);

      let bgmFile: string | null = null;
      if (bgmUrl) {
        bgmFile = await this.downloadFile(bgmUrl);
        tempFiles.push(bgmFile);
      }

      // 2. 合并音频和视频
      
      console.log(`[FFmpeg] 开始合并音视频...`);
      
      let cmd: string;
      if (bgmFile) {
        // 有背景音乐：混合主音频和背景音乐
        cmd = `ffmpeg -i "${videoFile}" -i "${audioFile}" -i "${bgmFile}" ` +
          `-filter_complex "[1:a]volume=1.0[a1];[2:a]volume=${bgmVolume}[a2];[a1][a2]amix=inputs=2:duration=first[a]" ` +
          `-map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k -shortest -y "${outputFile}"`;
      } else {
        // 无背景音乐：直接替换音频
        cmd = `ffmpeg -i "${videoFile}" -i "${audioFile}" ` +
          `-c:v copy -c:a aac -b:a 192k -map 0:v:0 -map 1:a:0 -shortest -y "${outputFile}"`;
      }

      await execAsync(cmd, { timeout: 600000 }); // 10分钟超时
      console.log(`[FFmpeg] 音视频合并成功: ${outputFile}`);

      // 3. 上传
      const videoId = uuidv4();
      const finalUrl = await storageService.uploadVideo(outputFile, videoId);
      console.log(`[FFmpeg] 上传完成: ${finalUrl}`);

      return finalUrl;
    } finally {
      // 4. 清理临时文件（下载的文件 + 合并输出文件）
      this.cleanupTempFiles([...tempFiles, outputFile]);
    }
  }

  /**
   * 获取音频时长（支持 OSS URL）
   * @param url 音频 URL
   * @returns 时长（秒）
   */
  static async getDuration(url: string): Promise<number> {
    const tempFiles: string[] = [];
    
    try {
      // 下载文件（如果是远程的）
      const localFile = await this.downloadFile(url);
      tempFiles.push(localFile);

      // 获取时长
      const cmd = `ffprobe -i "${localFile}" -show_entries format=duration -v quiet -of csv="p=0"`;
      const { stdout } = await execAsync(cmd, { timeout: 10000 });
      const duration = Math.round((parseFloat(stdout.trim()) || 0) * 10) / 10;
      
      console.log(`[FFmpeg] 音频时长: ${duration}秒`);
      return duration;
    } finally {
      this.cleanupTempFiles(tempFiles);
    }
  }

  /**
   * 转换音频格式
   * @param inputUrl 输入文件 URL
   * @param outputFormat 输出格式（mp3/wav/aac）
   * @param bitrate 比特率（默认 192k）
   * @returns 输出文件 URL
   */
  static async convertFormat(
    inputUrl: string,
    outputFormat: string = 'mp3',
    bitrate: string = '192k'
  ): Promise<string> {
    const tempFiles: string[] = [];
    const outputFile = path.join(TEMP_DIR, `${uuidv4()}_converted.${outputFormat}`);

    try {
      // 1. 下载文件
      const inputFile = await this.downloadFile(inputUrl);
      tempFiles.push(inputFile);

      // 2. 转换格式
      console.log(`[FFmpeg] 转换格式: ${inputUrl} -> ${outputFormat}`);
      
      let cmd: string;
      switch (outputFormat) {
        case 'mp3':
          cmd = `ffmpeg -i "${inputFile}" -c:a libmp3lame -b:a ${bitrate} -y "${outputFile}"`;
          break;
        case 'wav':
          cmd = `ffmpeg -i "${inputFile}" -c:a pcm_s16le -y "${outputFile}"`;
          break;
        case 'aac':
          cmd = `ffmpeg -i "${inputFile}" -c:a aac -b:a ${bitrate} -y "${outputFile}"`;
          break;
        default:
          throw new Error(`不支持的格式: ${outputFormat}`);
      }

      await execAsync(cmd, { timeout: 300000 });
      console.log(`[FFmpeg] 格式转换成功: ${outputFile}`);

      // 3. 上传
      const audioId = uuidv4();
      const finalUrl = await storageService.uploadAudio(outputFile, audioId);
      console.log(`[FFmpeg] 上传完成: ${finalUrl}`);

      return finalUrl;
    } finally {
      this.cleanupTempFiles([...tempFiles, outputFile]);
    }
  }

  /**
   * 裁剪音频
   * @param inputUrl 输入文件 URL
   * @param startTime 开始时间（秒）
   * @param duration 持续时间（秒）
   * @returns 裁剪后的文件 URL
   */
  static async trimAudio(
    inputUrl: string,
    startTime: number,
    duration: number
  ): Promise<string> {
    const tempFiles: string[] = [];
    const outputFile = path.join(TEMP_DIR, `${uuidv4()}_trimmed.mp3`);

    try {
      // 1. 下载文件
      const inputFile = await this.downloadFile(inputUrl);
      tempFiles.push(inputFile);

      // 2. 裁剪
      console.log(`[FFmpeg] 裁剪音频: ${startTime}s - ${startTime + duration}s`);
      
      const cmd = `ffmpeg -i "${inputFile}" -ss ${startTime} -t ${duration} ` +
        `-c:a libmp3lame -b:a 192k -y "${outputFile}"`;

      await execAsync(cmd, { timeout: 300000 });
      console.log(`[FFmpeg] 裁剪成功: ${outputFile}`);

      // 3. 上传
      const audioId = uuidv4();
      const finalUrl = await storageService.uploadAudio(outputFile, audioId);
      console.log(`[FFmpeg] 上传完成: ${finalUrl}`);

      return finalUrl;
    } finally {
      this.cleanupTempFiles([...tempFiles, outputFile]);
    }
  }

  /**
   * 调整音频音量
   * @param inputUrl 输入文件 URL
   * @param volume 音量倍数（1.0 = 原音量，0.5 = 减半，2.0 = 加倍）
   * @returns 处理后的文件 URL
   */
  static async adjustVolume(
    inputUrl: string,
    volume: number
  ): Promise<string> {
    const tempFiles: string[] = [];
    const outputFile = path.join(TEMP_DIR, `${uuidv4()}_volume.mp3`);

    try {
      // 1. 下载文件
      const inputFile = await this.downloadFile(inputUrl);
      tempFiles.push(inputFile);

      // 2. 调整音量
      console.log(`[FFmpeg] 调整音量: ${volume}x`);
      
      const cmd = `ffmpeg -i "${inputFile}" -filter:a "volume=${volume}" ` +
        `-c:a libmp3lame -b:a 192k -y "${outputFile}"`;

      await execAsync(cmd, { timeout: 300000 });
      console.log(`[FFmpeg] 音量调整成功: ${outputFile}`);

      // 3. 上传
      const audioId = uuidv4();
      const finalUrl = await storageService.uploadAudio(outputFile, audioId);
      console.log(`[FFmpeg] 上传完成: ${finalUrl}`);

      return finalUrl;
    } finally {
      this.cleanupTempFiles([...tempFiles, outputFile]);
    }
  }

  /**
   * 清理临时文件
   */
  private static cleanupTempFiles(files: string[]) {
    for (const file of files) {
      try {
        if (fs.existsSync(file)) {
          fs.unlinkSync(file);
          console.log(`[FFmpeg] 清理临时文件: ${file}`);
        }
      } catch (error) {
        console.warn(`[FFmpeg] 清理临时文件失败: ${file}`, error);
      }
    }
  }

  /**
   * 清理所有临时文件
   */
  static cleanupAllTempFiles() {
    try {
      if (fs.existsSync(TEMP_DIR)) {
        const files = fs.readdirSync(TEMP_DIR);
        for (const file of files) {
          const filePath = path.join(TEMP_DIR, file);
          fs.unlinkSync(filePath);
        }
        console.log(`[FFmpeg] 清理所有临时文件: ${files.length} 个`);
      }
    } catch (error) {
      console.error('[FFmpeg] 清理临时文件失败:', error);
    }
  }
}

export default FFmpegProcessor;
