import { spawn } from 'child_process';
import { stat } from 'fs/promises';
import { logger } from '../../../utils/logger';

export interface VideoMetadata {
  durationSeconds: number;
  width: number;
  height: number;
  orientation: 'H' | 'V';
  quality: '4K' | '1440p' | '1080p' | '720p' | '480p' | 'SD';
  hasAudio: boolean;
  fileSize: number;
}

interface FFprobeStream {
  codec_type: string;
  width?: number;
  height?: number;
  duration?: string;
}

interface FFprobeFormat {
  duration?: string;
}

interface FFprobeOutput {
  streams: FFprobeStream[];
  format: FFprobeFormat;
}

/**
 * Run a command with a timeout
 */
function runCommand(
  command: string,
  args: string[],
  timeoutMs: number = 30000
): Promise<string> {
  return new Promise((resolve, reject) => {
    const process = spawn(command, args);
    let stdout = '';
    let stderr = '';

    const timeout = setTimeout(() => {
      process.kill();
      reject(new Error(`Command timed out after ${timeoutMs}ms`));
    }, timeoutMs);

    process.stdout.on('data', (data) => {
      stdout += data.toString();
    });

    process.stderr.on('data', (data) => {
      stderr += data.toString();
    });

    process.on('close', (code) => {
      clearTimeout(timeout);
      if (code === 0) {
        resolve(stdout);
      } else {
        reject(new Error(`Command failed with code ${code}: ${stderr}`));
      }
    });

    process.on('error', (err) => {
      clearTimeout(timeout);
      reject(err);
    });
  });
}

/**
 * Extract video metadata using ffprobe
 */
export async function extractVideoMetadata(filePath: string): Promise<VideoMetadata> {
  try {
    // Run ffprobe to get video metadata
    const output = await runCommand('ffprobe', [
      '-v', 'quiet',
      '-print_format', 'json',
      '-show_streams',
      '-show_format',
      filePath,
    ]);

    const data: FFprobeOutput = JSON.parse(output);

    // Find video stream
    const videoStream = data.streams.find((s) => s.codec_type === 'video');
    if (!videoStream) {
      throw new Error('No video stream found in file');
    }

    // Find audio stream
    const hasAudio = data.streams.some((s) => s.codec_type === 'audio');

    // Extract dimensions
    const width = videoStream.width || 0;
    const height = videoStream.height || 0;

    if (width === 0 || height === 0) {
      throw new Error('Could not determine video dimensions');
    }

    // Calculate orientation
    const orientation: 'H' | 'V' = width >= height ? 'H' : 'V';

    // Calculate quality based on height
    let quality: '4K' | '1440p' | '1080p' | '720p' | '480p' | 'SD';
    if (height >= 2160) {
      quality = '4K';
    } else if (height >= 1440) {
      quality = '1440p';
    } else if (height >= 1080) {
      quality = '1080p';
    } else if (height >= 720) {
      quality = '720p';
    } else if (height >= 480) {
      quality = '480p';
    } else {
      quality = 'SD';
    }

    // Extract duration (prefer stream duration, fallback to format duration)
    let durationSeconds = 0;
    const streamDuration = videoStream.duration;
    const formatDuration = data.format.duration;

    if (streamDuration) {
      durationSeconds = parseFloat(streamDuration);
    } else if (formatDuration) {
      durationSeconds = parseFloat(formatDuration);
    }

    if (isNaN(durationSeconds) || durationSeconds <= 0) {
      throw new Error('Could not determine video duration');
    }

    // Get file size
    const stats = await stat(filePath);
    const fileSize = stats.size;

    logger.info(`Extracted metadata for ${filePath}:`, {
      width,
      height,
      orientation,
      quality,
      duration: durationSeconds,
      hasAudio,
      fileSize,
    });

    return {
      durationSeconds,
      width,
      height,
      orientation,
      quality,
      hasAudio,
      fileSize,
    };
  } catch (error) {
    logger.error(`Failed to extract metadata from ${filePath}:`, error);
    throw new Error(`Metadata extraction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
  }
}

/**
 * Validate video file by attempting to decode frames
 */
export async function validateVideoFile(filePath: string): Promise<boolean> {
  try {
    // Try to decode 5 frames
    await runCommand('ffmpeg', [
      '-v', 'error',
      '-i', filePath,
      '-vframes', '5',
      '-f', 'null',
      '-',
    ], 60000); // 60s timeout for validation

    logger.info(`Video validation successful: ${filePath}`);
    return true;
  } catch (error) {
    logger.warn(`Video validation failed for ${filePath}:`, error);
    return false;
  }
}
