use std::path::{Path, PathBuf};
use std::process::Command;
use std::fs;
use crate::types::Result;
use crate::utils::log::{success, info, warning};
use super::commands::{CompressArgs, VideoFormat};

const DEFAULT_OUTPUT_PREFIX: &str = "l-output-compress-video";

fn detect_input_format(input: &Path) -> Option<VideoFormat> {
    let ext = input.extension()?.to_str()?.to_lowercase();
    match ext.as_str() {
        "mp4" => Some(VideoFormat::Mp4),
        "mkv" => Some(VideoFormat::Mkv),
        "webm" => Some(VideoFormat::Webm),
        "mov" => Some(VideoFormat::Mov),
        "avi" => Some(VideoFormat::Avi),
        _ => None,
    }
}

fn get_video_info(input: &Path) -> Result<(u64, String)> {
    let metadata = fs::metadata(input)?;
    let size = metadata.len();
    
    let output = Command::new("ffprobe")
        .args([
            "-v", "quiet",
            "-print_format", "json",
            "-show_format",
            "-show_streams",
        ])
        .arg(input)
        .output()?;
    
    let info = String::from_utf8_lossy(&output.stdout).to_string();
    Ok((size, info))
}

fn build_output_path(input: &Path, output: Option<PathBuf>, format: &VideoFormat) -> PathBuf {
    if let Some(out) = output {
        return out;
    }
    
    let parent = input.parent().unwrap_or_else(|| Path::new("."));
    let output_name = format!("{}.{}", DEFAULT_OUTPUT_PREFIX, format.extension());
    parent.join(output_name)
}

fn compress_with_format(input: &Path, output: &Path, format: &VideoFormat) -> Result<u64> {
    let (codec, extra_args): (&str, Vec<&str>) = match format {
        VideoFormat::Mp4 => ("libx264", vec!["-crf", "18", "-preset", "slow"]),
        VideoFormat::Mkv => ("libx264", vec!["-crf", "18", "-preset", "slow"]),
        VideoFormat::Webm => ("libvpx-vp9", vec!["-crf", "31", "-b:v", "0"]),
        VideoFormat::Mov => ("libx264", vec!["-crf", "18", "-preset", "slow"]),
        VideoFormat::Avi => ("libx264", vec!["-crf", "18", "-preset", "slow"]),
    };
    
    let temp_output = output.with_extension(format!("temp.{}", format.extension()));
    
    let mut cmd = Command::new("ffmpeg");
    cmd.args(["-y", "-hide_banner", "-loglevel", "warning"])
        .args(["-i"])
        .arg(input)
        .args(["-c:v", codec])
        .args(&extra_args)
        .args(["-c:a", "copy"])
        .arg(&temp_output);
    
    let status = cmd.status()?;
    
    if !status.success() {
        if temp_output.exists() {
            let _ = fs::remove_file(&temp_output);
        }
        return Err(format!("FFmpeg compression failed for format {:?}", format).into());
    }
    
    let metadata = fs::metadata(&temp_output)?;
    let size = metadata.len();
    
    fs::rename(&temp_output, output)?;
    
    Ok(size)
}

fn try_all_formats_and_select_smallest(input: &Path, base_output: &Path) -> Result<(PathBuf, u64, VideoFormat)> {
    let formats = [
        VideoFormat::Mp4,
        VideoFormat::Mkv,
        VideoFormat::Webm,
    ];
    
    let parent = base_output.parent().unwrap_or_else(|| Path::new("."));
    let mut results: Vec<(PathBuf, u64, VideoFormat)> = Vec::new();
    
    info("Compressing video with multiple formats to find the smallest...");
    
    for format in &formats {
        let temp_output = parent.join(format!("{}.temp.{}", DEFAULT_OUTPUT_PREFIX, format.extension()));
        
        info(&format!("Trying {} format...", format.extension().to_uppercase()));
        
        match compress_single_format(input, &temp_output, format) {
            Ok(size) => {
                info(&format!("  {} compressed size: {} bytes ({:.2} MB)", 
                    format.extension().to_uppercase(), 
                    size, 
                    size as f64 / 1024.0 / 1024.0));
                results.push((temp_output, size, *format));
            }
            Err(e) => {
                warning(&format!("  {} compression failed: {}", format.extension().to_uppercase(), e));
            }
        }
    }
    
    if results.is_empty() {
        return Err("All compression formats failed".into());
    }
    
    results.sort_by_key(|(_, size, _)| *size);
    
    let (best_temp, best_size, best_format) = results.remove(0);
    
    for (temp_path, _, _) in results {
        if temp_path.exists() {
            let _ = fs::remove_file(&temp_path);
        }
    }
    
    let final_output = parent.join(format!("{}.{}", DEFAULT_OUTPUT_PREFIX, best_format.extension()));
    fs::rename(&best_temp, &final_output)?;
    
    Ok((final_output, best_size, best_format))
}

fn compress_single_format(input: &Path, output: &Path, format: &VideoFormat) -> Result<u64> {
    let (codec, extra_args): (&str, Vec<&str>) = match format {
        VideoFormat::Mp4 => ("libx264", vec!["-crf", "18", "-preset", "slow"]),
        VideoFormat::Mkv => ("libx264", vec!["-crf", "18", "-preset", "slow"]),
        VideoFormat::Webm => ("libvpx-vp9", vec!["-crf", "31", "-b:v", "0"]),
        VideoFormat::Mov => ("libx264", vec!["-crf", "18", "-preset", "slow"]),
        VideoFormat::Avi => ("libx264", vec!["-crf", "18", "-preset", "slow"]),
    };
    
    let mut cmd = Command::new("ffmpeg");
    cmd.args(["-y", "-hide_banner", "-loglevel", "warning"])
        .args(["-i"])
        .arg(input)
        .args(["-c:v", codec])
        .args(&extra_args)
        .args(["-c:a", "copy"])
        .arg(output);
    
    let status = cmd.status()?;
    
    if !status.success() {
        if output.exists() {
            let _ = fs::remove_file(output);
        }
        return Err(format!("FFmpeg compression failed for format {:?}", format).into());
    }
    
    let metadata = fs::metadata(output)?;
    Ok(metadata.len())
}

pub async fn execute(args: CompressArgs) -> Result<()> {
    let input = &args.input;
    
    if !input.exists() {
        return Err(format!("Input file not found: {}", input.display()).into());
    }
    
    let input_format = detect_input_format(input);
    if input_format.is_none() {
        return Err("Unsupported input video format. Supported: mp4, mkv, webm, mov, avi".into());
    }
    
    let (original_size, _) = get_video_info(input)?;
    info(&format!("Original video size: {} bytes ({:.2} MB)", 
        original_size, 
        original_size as f64 / 1024.0 / 1024.0));
    
    let (output_path, compressed_size, chosen_format) = if let Some(format) = args.format {
        let output = build_output_path(input, args.output, &format);
        
        if output.exists() {
            return Err(format!("Output file already exists: {}", output.display()).into());
        }
        
        if let Some(parent) = output.parent() {
            fs::create_dir_all(parent)?;
        }
        
        info(&format!("Compressing to {} format...", format.extension().to_uppercase()));
        let size = compress_with_format(input, &output, &format)?;
        (output, size, format)
    } else {
        let base_output = build_output_path(input, args.output.clone(), &VideoFormat::Mp4);
        
        if let Some(parent) = base_output.parent() {
            fs::create_dir_all(parent)?;
        }
        
        try_all_formats_and_select_smallest(input, &base_output)?
    };
    
    let reduction = if original_size > 0 {
        ((original_size as f64 - compressed_size as f64) / original_size as f64) * 100.0
    } else {
        0.0
    };
    
    println!();
    success(&format!("Compression complete!"));
    println!("  Format: {}", chosen_format.extension().to_uppercase());
    println!("  Output: {}", output_path.display());
    println!("  Original size:   {} bytes ({:.2} MB)", original_size, original_size as f64 / 1024.0 / 1024.0);
    println!("  Compressed size: {} bytes ({:.2} MB)", compressed_size, compressed_size as f64 / 1024.0 / 1024.0);
    println!("  Size reduction:  {:.1}%", reduction);
    
    Ok(())
}
