use crate::core::stream::exec_capture;
use crate::core::tracking;
use crate::core::utils::resolved_command;
use anyhow::{Context, Result};

/// Compact wget - strips progress bars, shows only result
pub fn run(url: &str, args: &[String], verbose: u8) -> Result<i32> {
    let timer = tracking::TimedExecution::start();

    if verbose > 0 {
        eprintln!("wget: {}", url);
    }

    // Run wget normally but capture output to parse it
    let mut cmd_args: Vec<&str> = vec![];

    // Add user args
    for arg in args {
        cmd_args.push(arg);
    }
    cmd_args.push(url);

    let mut cmd = resolved_command("wget");
    cmd.args(&cmd_args);
    let result = exec_capture(&mut cmd).context("Failed to run wget")?;

    let raw_output = format!("{}\n{}", result.stderr, result.stdout);

    if result.success() {
        let filename = extract_filename_from_output(&result.stderr, url, args);
        let size = get_file_size(&filename);
        let msg = format!(
            "{} ok | {} | {}",
            compact_url(url),
            filename,
            format_size(size)
        );
        println!("{}", msg);
        timer.track(&format!("wget {}", url), "rtk wget", &raw_output, &msg);
    } else {
        let error = parse_error(&result.stderr, &result.stdout);
        let msg = format!("{} FAILED: {}", compact_url(url), error);
        println!("{}", msg);
        timer.track(&format!("wget {}", url), "rtk wget", &raw_output, &msg);
        return Ok(result.exit_code);
    }

    Ok(0)
}

/// Run wget and output to stdout (for piping)
pub fn run_stdout(url: &str, args: &[String], verbose: u8) -> Result<i32> {
    let timer = tracking::TimedExecution::start();

    if verbose > 0 {
        eprintln!("wget: {} -> stdout", url);
    }

    let mut cmd_args = vec!["-q", "-O", "-"];
    for arg in args {
        cmd_args.push(arg);
    }
    cmd_args.push(url);

    let mut cmd = resolved_command("wget");
    cmd.args(&cmd_args);
    let result = exec_capture(&mut cmd).context("Failed to run wget")?;

    if result.success() {
        let lines: Vec<&str> = result.stdout.lines().collect();
        let total = lines.len();

        let mut rtk_output = String::new();
        if total > 20 {
            rtk_output.push_str(&format!(
                "{} ok | {} lines | {}\n",
                compact_url(url),
                total,
                format_size(result.stdout.len() as u64)
            ));
            rtk_output.push_str("first 10 lines:\n");
            for line in lines.iter().take(10) {
                rtk_output.push_str(&format!("{}\n", truncate_line(line, 100)));
            }
            rtk_output.push_str(&format!("... +{} more lines", total - 10));
        } else {
            rtk_output.push_str(&format!("{} ok | {} lines\n", compact_url(url), total));
            for line in &lines {
                rtk_output.push_str(&format!("{}\n", line));
            }
        }
        print!("{}", rtk_output);
        timer.track(
            &format!("wget -O - {}", url),
            "rtk wget -o",
            &result.stdout,
            &rtk_output,
        );
    } else {
        let error = parse_error(&result.stderr, "");
        let msg = format!("{} FAILED: {}", compact_url(url), error);
        println!("{}", msg);
        timer.track(&format!("wget -O - {}", url), "rtk wget -o", &result.stderr, &msg);
        return Ok(result.exit_code);
    }

    Ok(0)
}

fn extract_filename_from_output(stderr: &str, url: &str, args: &[String]) -> String {
    // Check for -O argument first
    for (i, arg) in args.iter().enumerate() {
        if arg == "-O" || arg == "--output-document" {
            if let Some(name) = args.get(i + 1) {
                return name.clone();
            }
        }
        if let Some(name) = arg.strip_prefix("-O") {
            return name.to_string();
        }
    }

    // Parse wget output for "Sauvegarde en" or "Saving to"
    for line in stderr.lines() {
        // French: Sauvegarde en : « filename »
        if line.contains("Sauvegarde en") || line.contains("Saving to") {
            // Use char-based parsing to handle Unicode properly
            let chars: Vec<char> = line.chars().collect();
            let mut start_idx = None;
            let mut end_idx = None;

            for (i, c) in chars.iter().enumerate() {
                if *c == '«' || (*c == '\'' && start_idx.is_none()) {
                    start_idx = Some(i);
                }
                if *c == '»' || (*c == '\'' && start_idx.is_some()) {
                    end_idx = Some(i);
                }
            }

            if let (Some(s), Some(e)) = (start_idx, end_idx) {
                if e > s + 1 {
                    let filename: String = chars[s + 1..e].iter().collect();
                    return filename.trim().to_string();
                }
            }
        }
    }

    // Fallback: extract from URL
    let path = url.rsplit("://").next().unwrap_or(url);
    let filename = path
        .rsplit('/')
        .next()
        .unwrap_or("index.html")
        .split('?')
        .next()
        .unwrap_or("index.html");

    if filename.is_empty() || !filename.contains('.') {
        "index.html".to_string()
    } else {
        filename.to_string()
    }
}

fn get_file_size(filename: &str) -> u64 {
    std::fs::metadata(filename).map(|m| m.len()).unwrap_or(0)
}

fn format_size(bytes: u64) -> String {
    if bytes == 0 {
        return "?".to_string();
    }
    if bytes < 1024 {
        format!("{}B", bytes)
    } else if bytes < 1024 * 1024 {
        format!("{:.1}KB", bytes as f64 / 1024.0)
    } else if bytes < 1024 * 1024 * 1024 {
        format!("{:.1}MB", bytes as f64 / (1024.0 * 1024.0))
    } else {
        format!("{:.1}GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
    }
}

fn compact_url(url: &str) -> String {
    // Remove protocol
    let without_proto = url
        .strip_prefix("https://")
        .or_else(|| url.strip_prefix("http://"))
        .unwrap_or(url);

    // Truncate if too long
    let chars: Vec<char> = without_proto.chars().collect();
    if chars.len() <= 50 {
        without_proto.to_string()
    } else {
        let prefix: String = chars[..25].iter().collect();
        let suffix: String = chars[chars.len() - 20..].iter().collect();
        format!("{}...{}", prefix, suffix)
    }
}

#[allow(dead_code)]
fn parse_error(stderr: &str, stdout: &str) -> String {
    // Common wget error patterns
    let combined = format!("{}\n{}", stderr, stdout);

    if combined.contains("404") {
        return "404 Not Found".to_string();
    }
    if combined.contains("403") {
        return "403 Forbidden".to_string();
    }
    if combined.contains("401") {
        return "401 Unauthorized".to_string();
    }
    if combined.contains("500") {
        return "500 Server Error".to_string();
    }
    if combined.contains("Connection refused") {
        return "Connection refused".to_string();
    }
    if combined.contains("unable to resolve") || combined.contains("Name or service not known") {
        return "DNS lookup failed".to_string();
    }
    if combined.contains("timed out") {
        return "Connection timed out".to_string();
    }
    if combined.contains("SSL") || combined.contains("certificate") {
        return "SSL/TLS error".to_string();
    }

    // Return first meaningful line
    for line in stderr.lines() {
        let trimmed = line.trim();
        if !trimmed.is_empty() && !trimmed.starts_with("--") {
            if trimmed.len() > 60 {
                let t: String = trimmed.chars().take(60).collect();
                return format!("{}...", t);
            }
            return trimmed.to_string();
        }
    }

    "Unknown error".to_string()
}

fn truncate_line(line: &str, max: usize) -> String {
    if line.len() <= max {
        line.to_string()
    } else {
        let t: String = line.chars().take(max.saturating_sub(3)).collect();
        format!("{}...", t)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_compact_url_strips_protocol() {
        assert_eq!(compact_url("https://example.com/file.zip"), "example.com/file.zip");
        assert_eq!(compact_url("http://example.com/file.zip"), "example.com/file.zip");
    }

    #[test]
    fn test_compact_url_truncates_long_url() {
        let long = "https://example.com/very/long/path/that/exceeds/fifty/characters/file.zip";
        let result = compact_url(long);
        assert!(result.contains("..."), "Long URL should be truncated with ...");
        assert!(result.len() < long.len());
    }

    #[test]
    fn test_compact_url_short_unchanged() {
        let short = "https://x.com/f";
        assert_eq!(compact_url(short), "x.com/f");
    }

    #[test]
    fn test_format_size_zero() {
        assert_eq!(format_size(0), "?");
    }

    #[test]
    fn test_format_size_bytes() {
        assert_eq!(format_size(512), "512B");
    }

    #[test]
    fn test_format_size_kilobytes() {
        let result = format_size(2048);
        assert!(result.ends_with("KB"), "Expected KB, got {}", result);
    }

    #[test]
    fn test_format_size_megabytes() {
        let result = format_size(2 * 1024 * 1024);
        assert!(result.ends_with("MB"), "Expected MB, got {}", result);
    }

    #[test]
    fn test_parse_error_404() {
        assert_eq!(parse_error("HTTP request failed: 404", ""), "404 Not Found");
    }

    #[test]
    fn test_parse_error_dns() {
        assert_eq!(
            parse_error("unable to resolve host example.com", ""),
            "DNS lookup failed"
        );
    }

    #[test]
    fn test_parse_error_ssl() {
        assert_eq!(
            parse_error("SSL certificate verification failed", ""),
            "SSL/TLS error"
        );
    }

    #[test]
    fn test_parse_error_unknown() {
        assert_eq!(parse_error("", ""), "Unknown error");
    }

    #[test]
    fn test_truncate_line_short() {
        assert_eq!(truncate_line("hello", 10), "hello");
    }

    #[test]
    fn test_truncate_line_exact() {
        assert_eq!(truncate_line("hello", 5), "hello");
    }

    #[test]
    fn test_truncate_line_long() {
        let result = truncate_line("hello world this is long", 10);
        assert!(result.ends_with("..."));
        assert!(result.len() <= 10);
    }

    #[test]
    fn test_extract_filename_from_output_flag() {
        let args = vec!["-O".to_string(), "myfile.zip".to_string()];
        assert_eq!(
            extract_filename_from_output("", "https://example.com/x", &args),
            "myfile.zip"
        );
    }

    #[test]
    fn test_extract_filename_from_url_fallback() {
        let result = extract_filename_from_output("", "https://example.com/file.tar.gz", &[]);
        assert_eq!(result, "file.tar.gz");
    }

    #[test]
    fn test_extract_filename_empty_url_fallback() {
        let result = extract_filename_from_output("", "https://example.com/", &[]);
        assert_eq!(result, "index.html");
    }
}
