import path, { dirname } from 'path';
import ffmpeg from 'fluent-ffmpeg';
import { FileMetadata, VideoMetadata, ResourceProcessor } from '../types';
import { Resource, ResourceStatus, db } from '@nice/common';
import { getUploadFilePath } from '@server/utils/file';
import fs from 'fs/promises';
import sharp from 'sharp';
import { BaseProcessor } from './BaseProcessor';

export class VideoProcessor extends BaseProcessor {
  constructor() {
    super();
  }
  async process(resource: Resource): Promise<Resource> {
    console.log('process');
    const { url } = resource;
    const filepath = getUploadFilePath(url);
    this.logger.log(
      `Processing video for resource ID: ${resource.id}, File ID: ${url}`,
    );

    const originMeta = resource.meta as unknown as FileMetadata;
    if (!originMeta.filetype?.startsWith('video/')) {
      this.logger.log(`Skipping non-video resource: ${resource.id}`);
      return resource;
    }

    try {
      const streamDir = this.createOutputDir(filepath, 'stream');

      const [m3u8Path, videoMetadata, coverUrl] = await Promise.all([
        this.generateM3U8Stream(filepath, streamDir),
        this.getVideoMetadata(filepath),
        this.generateVideoCover(filepath, dirname(filepath)),
      ]);
      const videoMeta: VideoMetadata = {
        ...videoMetadata,
        coverUrl: coverUrl,
      };
      const updatedResource = await db.resource.update({
        where: { id: resource.id },
        data: {
          meta: {
            ...originMeta,
            ...videoMeta,
          },
        },
      });
      this.logger.log(
        `Successfully processed video for resource ID: ${resource.id}`,
      );
      return updatedResource;
    } catch (error: any) {
      this.logger.error(
        `Failed to process video for resource ID: ${resource.id}, Error: ${error.message}`,
      );
      throw new Error(`Failed to process video: ${error.message}`);
    }
  }
  private async generateVideoCover(
    filepath: string,
    outputDir: string,
  ): Promise<string> {
    this.logger.log(`Generating video cover for: ${filepath}`);
    const jpgCoverPath = path.join(outputDir, 'cover.jpg');
    const webpCoverPath = path.join(outputDir, 'cover.webp');
    return new Promise((resolve, reject) => {
      ffmpeg(filepath)
        .on('end', async () => {
          try {
            // 使用 Sharp 将 JPG 转换为 WebP
            await sharp(jpgCoverPath)
              .webp({ quality: 80 }) // 设置 WebP 压缩质量
              .toFile(webpCoverPath);

            // 删除临时 JPG 文件
            await fs.unlink(jpgCoverPath);
            this.logger.log(`Video cover generated at: ${webpCoverPath}`);
            resolve(path.basename(webpCoverPath));
          } catch (error: any) {
            this.logger.error(
              `Error converting cover to WebP: ${error.message}`,
            );
            reject(error);
          }
        })
        .on('error', (err) => {
          this.logger.error(`Error generating video cover: ${err.message}`);
          reject(err);
        })
        .screenshots({
          count: 1,
          folder: outputDir,
          filename: 'cover.jpg',
          size: '640x360',
        });
    });
  }
  private async getVideoDuration(filepath: string): Promise<number> {
    this.logger.log(`Getting video duration for file: ${filepath}`);
    return new Promise((resolve, reject) => {
      ffmpeg.ffprobe(filepath, (err, metadata) => {
        if (err) {
          this.logger.error(`Error getting video duration: ${err.message}`);
          reject(err);
          return;
        }
        const duration = metadata.format.duration || 0;
        this.logger.log(`Video duration: ${duration} seconds`);
        resolve(duration);
      });
    });
  }
  private async generateM3U8Stream(
    filepath: string,
    outputDir: string,
  ): Promise<string> {
    console.log('outputDir', outputDir);
    const m3u8Path = path.join(outputDir, 'index.m3u8');
    this.logger.log(
      `Generating M3U8 stream for video: ${filepath}, Output Dir: ${outputDir}`,
    );
    return new Promise<string>((resolve, reject) => {
      ffmpeg(filepath)
        .outputOptions([
          // Improved video encoding settings
          '-c:v libx264',
          '-preset medium', // Balance between encoding speed and compression
          '-crf 23', // Constant Rate Factor for quality
          '-profile:v high', // Higher profile for better compression
          '-level:v 4.1', // Updated level for better compatibility
          // Parallel processing and performance
          '-threads 0', // Auto-detect optimal thread count
          '-x264-params keyint=48:min-keyint=48', // More precise GOP control
          // HLS specific optimizations
          '-hls_time 4', // Shorter segment duration for better adaptive streaming
          '-hls_list_size 0', // Keep all segments in playlist
          '-hls_flags independent_segments+delete_segments', // Allow segment cleanup
          // Additional encoding optimizations
          '-sc_threshold 0', // Disable scene change detection for more consistent segments
          '-max_muxing_queue_size 1024', // Increase muxing queue size
          // Output format
          '-f hls',
        ])
        .output(m3u8Path)
        .on('start', (commandLine) => {
          this.logger.log(`Starting ffmpeg with command: ${commandLine}`);
        })
        .on('end', () => {
          this.logger.log(`Successfully generated M3U8 stream at: ${m3u8Path}`);
          resolve(m3u8Path);
        })
        .on('error', (err) => {
          const errorMessage = `Error generating M3U8 stream for ${filepath}: ${err.message}`;
          this.logger.error(errorMessage);
          reject(new Error(errorMessage));
        })
        .run();
    });
  }
  private async getVideoMetadata(
    filepath: string,
  ): Promise<Partial<VideoMetadata>> {
    this.logger.log(`Getting video metadata for file: ${filepath}`);
    return new Promise((resolve, reject) => {
      ffmpeg.ffprobe(filepath, (err, metadata) => {
        if (err) {
          this.logger.error(`Error getting video metadata: ${err.message}`);
          reject(err);
          return;
        }
        const videoStream = metadata.streams.find(
          (stream) => stream.codec_type === 'video',
        );
        const audioStream = metadata.streams.find(
          (stream) => stream.codec_type === 'audio',
        );
        const videoMetadata: Partial<VideoMetadata> = {
          width: videoStream?.width || 0,
          height: videoStream?.height || 0,
          duration: metadata.format.duration || 0,
          videoCodec: videoStream?.codec_name || '',
          audioCodec: audioStream?.codec_name || '',
        };
        this.logger.log(
          `Extracted video metadata: ${JSON.stringify(videoMetadata)}`,
        );
        resolve(videoMetadata);
      });
    });
  }
}
