// filepath: src-tauri/src/downloader.rs
use tauri::{AppHandle, Emitter};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use std::process::Stdio;
use serde::{Deserialize, Serialize};
use anyhow::{Result, anyhow};
use regex::Regex;
use crate::binary_manager;
use crate::storage;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct VideoMetadata {
    pub id: String,
    pub title: String,
    pub thumbnail: String,
    pub duration: Option<f64>,
    pub uploader: Option<String>,
    pub url: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PlaylistMetadata {
    pub id: String,
    pub title: String,
    pub entries: Vec<VideoMetadata>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum MetadataResult {
    Video(VideoMetadata),
    Playlist(PlaylistMetadata),
}

#[derive(Deserialize, Debug, Clone)]
pub struct DownloadOptions {
    pub is_audio_only: bool,
    pub quality: String, // e.g., "1080", "720", "best"
    pub output_path: String, // Directory
    pub output_format: String, // "original", "mp4", "webm", "mkv", "m4a", "aac", "opus", "vorbis", "wav", etc.
    pub cookies_path: Option<String>,
}

#[derive(Serialize, Clone, Debug)]
pub struct ProgressEvent {
    pub id: String,
    pub progress: f64,
    pub speed: String,
    pub status: String, // "downloading", "processing", "finished", "error"
}

#[derive(Serialize, Clone, Debug)]
pub struct LogEvent {
    pub id: String,
    pub message: String,
    pub level: String, // "info", "error"
}



pub async fn fetch_metadata(app: &AppHandle, url: &str, parse_mix_playlist: bool) -> Result<MetadataResult> {
    app.emit("download-log", LogEvent {
        id: "Analysis".to_string(),
        message: format!("正在为 URL: {} 获取元数据", url),
        level: "info".to_string(),
    }).ok();

    let ytdlp_path = binary_manager::get_ytdlp_path(app)?;
    let qjs_path = binary_manager::get_qjs_path(app)?; // Get absolute path to quickjs
    
    // Load settings to check for cookies
    let settings = storage::load_settings(app)?;
    
    let mut args = Vec::new();

    // Pass the runtime and its absolute path to --js-runtimes
    // Rust's Command automatically handles spaces in arguments, so we should NOT quote the path here.
    args.push("--js-runtimes".to_string());
    args.push(format!("quickjs:{}", qjs_path.to_string_lossy())); 

    let mut has_cookies = false;
    if let Some(cookies) = &settings.cookies_path {
        if !cookies.is_empty() {
             if std::path::Path::new(cookies).exists() {
                 args.push("--cookies".to_string());
                 args.push(cookies.clone());
                 has_cookies = true;
                 app.emit("download-log", LogEvent {
                    id: "Analysis".to_string(),
                    message: format!("已加载 Cookies: {}", cookies),
                    level: "info".to_string(),
                 }).ok();
             } else {
                 app.emit("download-log", LogEvent {
                    id: "Analysis".to_string(),
                    message: format!("Cookies 文件不存在: {}", cookies),
                    level: "error".to_string(),
                 }).ok();
             }
        }
    }

    args.push("--dump-single-json".to_string());
    args.push("--flat-playlist".to_string());
    args.push("--no-warnings".to_string());

    if has_cookies {
        // When using cookies, avoid skipping JS player to prevent challenge errors
        args.push("--extractor-args".to_string());
        args.push("youtube:skip=dash,hls,translated_subs".to_string());
    } else {
        // Optimize metadata fetching: skip heavy manifests and player JS execution.
        // Skipping JS prevents slow QuickJS spin-up and signature decryption, drastically speeding up single video parsing.
        args.push("--extractor-args".to_string());
        args.push("youtube:skip=dash,hls,translated_subs;player_skip=js".to_string());
    }

    if parse_mix_playlist {
        args.push("--playlist-end".to_string());
        args.push("20".to_string());
    }

    args.push(url.to_string());

    let mut cmd = Command::new(&ytdlp_path);
    #[cfg(target_os = "windows")]
    cmd.creation_flags(0x08000000);
    
    cmd.args(&args);
    cmd.stderr(Stdio::piped());
    
    // Log the full command
    let full_cmd_str = format!("{} {}", ytdlp_path.to_string_lossy(), args.join(" "));
    app.emit("download-log", LogEvent {
        id: "Analysis".to_string(),
        message: format!("正在执行分析命令: {}", full_cmd_str),
        level: "info".to_string(),
    }).ok();

    let output = cmd.output().await?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        app.emit("download-log", LogEvent {
            id: "Analysis".to_string(),
            message: format!("元数据获取失败: {}", stderr),
            level: "error".to_string(),
        }).ok();
        return Err(anyhow!("yt-dlp error: {}", stderr));
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    let json: serde_json::Value = serde_json::from_str(&stdout)?;

    // Check if playlist
    if let Some(_type) = json.get("_type") {
        if _type == "playlist" {
            let entries_json = json["entries"].as_array().ok_or(anyhow!("No entries in playlist"))?;
            let mut entries = Vec::new();
            
            for entry in entries_json {
                entries.push(parse_video_metadata(entry));
            }

            let result = MetadataResult::Playlist(PlaylistMetadata {
                id: json["id"].as_str().unwrap_or("").to_string(),
                title: json["title"].as_str().unwrap_or("Unknown Playlist").to_string(),
                entries,
            });

            app.emit("download-log", LogEvent {
                id: "Analysis".to_string(),
                message: "元数据获取成功（播放列表）".to_string(),
                level: "info".to_string(),
            }).ok();

            return Ok(result);
        }
    }

    // Single video
    let result = MetadataResult::Video(parse_video_metadata(&json));
    app.emit("download-log", LogEvent {
        id: "Analysis".to_string(),
        message: "元数据获取成功（视频）".to_string(),
        level: "info".to_string(),
    }).ok();

    Ok(result)
}

fn parse_video_metadata(json: &serde_json::Value) -> VideoMetadata {
    let id = json["id"].as_str().unwrap_or("").to_string();
    
    // Thumbnail fallback logic
    let thumbnail = match json.get("thumbnail").and_then(|t| t.as_str()) {
        Some(t) if !t.is_empty() => t.to_string(),
        _ => format!("https://i.ytimg.com/vi/{}/mqdefault.jpg", id),
    };

    let url = json["webpage_url"].as_str()
        .or_else(|| json["url"].as_str())
        .map(|s| s.to_string());

    VideoMetadata {
        id,
        title: json["title"].as_str().unwrap_or("Unknown Title").to_string(),
        thumbnail,
        duration: json["duration"].as_f64(),
        uploader: json["uploader"].as_str().map(|s| s.to_string()),
        url,
    }
}

pub async fn download_video(
    app: AppHandle,
    id: String, // Unique ID for this download task (provided by frontend)
    url: String,
    options: DownloadOptions,
) -> Result<String> {
    let ytdlp_path = binary_manager::get_ytdlp_path(&app)?;
    let qjs_path = binary_manager::get_qjs_path(&app)?; // Get absolute path to quickjs
    let ffmpeg_path = binary_manager::get_ffmpeg_path(&app)?; // Get absolute path to ffmpeg
    
    let mut args = Vec::new();
    
    // Pass the runtime and its absolute path to --js-runtimes
    args.push("--js-runtimes".to_string());
    // Rust's Command automatically handles spaces in arguments, so we should NOT quote the path here.
    args.push(format!("quickjs:{}", qjs_path.to_string_lossy()));
    // Pass ffmpeg location so yt-dlp can find our managed ffmpeg
    args.push("--ffmpeg-location".to_string());
    args.push(ffmpeg_path.to_string_lossy().to_string());

    if let Some(cookies) = &options.cookies_path {
        if !cookies.is_empty() {
             args.push("--cookies".to_string());
             args.push(cookies.clone());
        }
    }

    args.push(url);
    
    // Output template
    let output_template = format!("{}/%(title)s.%(ext)s", options.output_path.trim_end_matches(std::path::MAIN_SEPARATOR));
    args.push("-o".to_string());
    args.push(output_template);

    // Formats
    if options.is_audio_only {
        args.push("-x".to_string());
        // Only set audio format if not "original"
        if options.output_format != "original" {
            args.push("--audio-format".to_string());
            args.push(options.output_format.clone());
        }
    } else {
        let format_arg = if options.quality == "best" {
            "bestvideo+bestaudio/best".to_string()
        } else {
            format!("bestvideo[height<={}]+bestaudio/best[height<={}]/best", options.quality, options.quality)
        };
        args.push("-f".to_string());
        args.push(format_arg);
        
        // Only set merge output format if not "original"
        if options.output_format != "original" {
            args.push("--merge-output-format".to_string());
            args.push(options.output_format.clone());
        }
    }

    // Progress output
    args.push("--newline".to_string());

    // Log the full command
    let full_cmd_str = format!("{} {}", ytdlp_path.to_string_lossy(), args.join(" "));
    app.emit("download-log", LogEvent {
        id: id.clone(),
        message: format!("正在执行命令: {}", full_cmd_str),
        level: "info".to_string(),
    }).ok();

    let mut cmd = Command::new(ytdlp_path);
    #[cfg(target_os = "windows")]
    cmd.creation_flags(0x08000000);
    
    let mut child = cmd
        .args(&args)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;

    let stdout = child.stdout.take().ok_or(anyhow!("Failed to open stdout"))?;
    let stderr = child.stderr.take().ok_or(anyhow!("Failed to open stderr"))?;

    let mut stdout_reader = BufReader::new(stdout);
    let mut stderr_reader = BufReader::new(stderr);

    let re = Regex::new(r"\[download\]\s+(\d+\.?\d*)%").unwrap();

    // Loop to read both streams
    loop {
        let mut out_line = String::new();
        let mut err_line = String::new();

        tokio::select! {
            res = stdout_reader.read_line(&mut out_line) => {
                if res.unwrap_or(0) == 0 {
                    break; // EOF
                }
                
                // Parse progress
                if let Some(caps) = re.captures(&out_line) {
                    if let Some(pct_match) = caps.get(1) {
                        if let Ok(pct) = pct_match.as_str().parse::<f64>() {
                            app.emit("download-progress", ProgressEvent {
                                id: id.clone(),
                                progress: pct,
                                speed: "待定".to_string(),
                                status: "downloading".to_string(),
                            }).ok();
                        }
                    }
                } else { // Only emit download-log if it's NOT a progress line
                     app.emit("download-log", LogEvent {
                        id: id.clone(),
                        message: out_line.trim().to_string(),
                        level: "info".to_string(),
                    }).ok();
                }
            }
            res = stderr_reader.read_line(&mut err_line) => {
                 if res.unwrap_or(0) > 0 {
                     // Log error
                    app.emit("download-log", LogEvent {
                        id: id.clone(),
                        message: err_line.trim().to_string(),
                        level: "error".to_string(),
                    }).ok();
                 }
            }
        }
    }

    let status = child.wait().await?;

    if status.success() {
         app.emit("download-progress", ProgressEvent {
            id: id.clone(),
            progress: 100.0,
            speed: "-".to_string(),
            status: "finished".to_string(),
        }).ok();
        Ok("下载完成".to_string())
    } else {
         app.emit("download-progress", ProgressEvent {
            id: id.clone(),
            progress: 0.0,
            speed: "-".to_string(),
            status: "error".to_string(),
        }).ok();
        Err(anyhow!("下载进程失败"))
    }
}
