import { existsSync, mkdirSync } from 'fs';
import { readFile, writeFile } from 'fs/promises';
import path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

// Thumbnail cache directory (relative to project root)
const THUMBNAIL_CACHE_DIR = path.join(process.cwd(), '.cache', 'video-thumbnails');
const THUMBNAIL_QUALITY = 85; // JPEG quality
const THUMBNAIL_MAX_WIDTH = 800; // Max width for thumbnails

// Ensure cache directory exists
function ensureCacheDir() {
  if (!existsSync(THUMBNAIL_CACHE_DIR)) {
    mkdirSync(THUMBNAIL_CACHE_DIR, { recursive: true });
  }
}

/**
 * Get thumbnail path for a video file
 */
function getThumbnailPath(videoPath: string): string {
  ensureCacheDir();
  // Create a hash-like filename from the video path
  // Use a simple approach: replace path separators and special chars
  const safePath = videoPath.replace(/[^a-zA-Z0-9]/g, '_');
  const hash = Buffer.from(safePath).toString('base64').slice(0, 32);
  return path.join(THUMBNAIL_CACHE_DIR, `${hash}.jpg`);
}

/**
 * Check if ffmpeg is available
 */
async function isFfmpegAvailable(): Promise<boolean> {
  try {
    await execAsync('ffmpeg -version');
    return true;
  } catch {
    return false;
  }
}

/**
 * Generate video thumbnail using ffmpeg
 * Extracts frame at 1 second (or first frame if video is shorter)
 */
async function generateThumbnailWithFfmpeg(
  videoPath: string,
  thumbnailPath: string
): Promise<Buffer> {
  // Escape paths properly for shell commands
  const escapedVideoPath = videoPath.replace(/'/g, "'\"'\"'");
  const escapedThumbnailPath = thumbnailPath.replace(/'/g, "'\"'\"'");
  
  // Extract frame at 1 second, or first frame if video is shorter
  // Scale to max width while maintaining aspect ratio
  // Use -loglevel error to suppress ffmpeg output unless there's an error
  const command = `ffmpeg -i '${escapedVideoPath}' -ss 00:00:01 -vframes 1 -vf "scale=${THUMBNAIL_MAX_WIDTH}:-1" -q:v ${THUMBNAIL_QUALITY} -y '${escapedThumbnailPath}' -loglevel error`;
  
  try {
    const { stdout, stderr } = await execAsync(command, { maxBuffer: 10 * 1024 * 1024 });
    
    if (stderr && !stderr.includes('frame=')) {
      console.warn('ffmpeg stderr:', stderr);
    }
    
    // Wait a bit for file to be written
    await new Promise(resolve => setTimeout(resolve, 100));
    
    // Read the generated thumbnail
    if (existsSync(thumbnailPath)) {
      const buffer = await readFile(thumbnailPath);
      if (buffer.length > 0) {
        return buffer;
      }
      throw new Error('Thumbnail file is empty');
    }
    throw new Error('Thumbnail file was not created');
  } catch (error) {
    console.error(`Error extracting frame at 1s for ${videoPath}:`, error);
    
    // If extraction at 1s fails, try first frame
    try {
      const fallbackCommand = `ffmpeg -i '${escapedVideoPath}' -vframes 1 -vf "scale=${THUMBNAIL_MAX_WIDTH}:-1" -q:v ${THUMBNAIL_QUALITY} -y '${escapedThumbnailPath}' -loglevel error`;
      const { stdout, stderr } = await execAsync(fallbackCommand, { maxBuffer: 10 * 1024 * 1024 });
      
      if (stderr && !stderr.includes('frame=')) {
        console.warn('ffmpeg fallback stderr:', stderr);
      }
      
      // Wait a bit for file to be written
      await new Promise(resolve => setTimeout(resolve, 100));
      
      if (existsSync(thumbnailPath)) {
        const buffer = await readFile(thumbnailPath);
        if (buffer.length > 0) {
          return buffer;
        }
        throw new Error('Fallback thumbnail file is empty');
      }
    } catch (fallbackError) {
      console.error(`Error generating video thumbnail (fallback failed) for ${videoPath}:`, fallbackError);
      throw new Error(`Failed to generate video thumbnail: ${fallbackError instanceof Error ? fallbackError.message : 'Unknown error'}`);
    }
    throw error;
  }
}

/**
 * Generate or retrieve cached video thumbnail
 * Returns thumbnail buffer if successful, null if ffmpeg is not available
 */
export async function getVideoThumbnail(videoPath: string): Promise<Buffer | null> {
  // Check if ffmpeg is available
  const ffmpegAvailable = await isFfmpegAvailable();
  if (!ffmpegAvailable) {
    console.warn('ffmpeg is not available. Video thumbnails will not be generated.');
    return null;
  }

  const thumbnailPath = getThumbnailPath(videoPath);

  // Check if thumbnail already exists in cache
  if (existsSync(thumbnailPath)) {
    try {
      return await readFile(thumbnailPath);
    } catch (error) {
      console.error('Error reading cached thumbnail:', error);
      // Continue to regenerate
    }
  }

  // Check if video file exists
  if (!existsSync(videoPath)) {
    console.error(`Video file not found: ${videoPath}`);
    return null;
  }

  // Generate new thumbnail
  try {
    const thumbnailBuffer = await generateThumbnailWithFfmpeg(videoPath, thumbnailPath);
    return thumbnailBuffer;
  } catch (error) {
    console.error(`Error generating thumbnail for ${videoPath}:`, error);
    return null;
  }
}

/**
 * Check if a thumbnail exists in cache
 */
export function hasCachedThumbnail(videoPath: string): boolean {
  const thumbnailPath = getThumbnailPath(videoPath);
  return existsSync(thumbnailPath);
}

/**
 * Get cached thumbnail path (for direct file serving)
 */
export function getCachedThumbnailPath(videoPath: string): string | null {
  const thumbnailPath = getThumbnailPath(videoPath);
  return existsSync(thumbnailPath) ? thumbnailPath : null;
}

