use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;

// Progress lines from Trivy
static PROGRESS_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^(Downloading|Updating|Fetching|Loading|Detecting|Checking) [^\n]+\n?")
        .unwrap()
});

pub fn compress_trivy(subcmd: &str, raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // JSON output mode
    if cleaned.trim_start().starts_with('{') && cleaned.contains("\"Vulnerabilities\"") {
        return compress_trivy_json(&cleaned);
    }
    let _ = subcmd; // same logic for image/fs/repo
    compress_trivy_text(&cleaned)
}

fn compress_trivy_text(raw: &str) -> String {
    let s = PROGRESS_RE.replace_all(raw, "");

    let mut critical: Vec<String> = Vec::new();
    let mut high: Vec<String> = Vec::new();
    let mut medium_count = 0usize;
    let mut low_count = 0usize;
    let mut other_lines: Vec<&str> = Vec::new();

    for line in s.lines() {
        let t = line.trim();
        if t.is_empty() {
            continue;
        }
        // Skip summary/metadata lines before doing keyword matching
        if t.starts_with("Total:") || t.starts_with("Legend:") || t.contains("scan") {
            continue;
        }
        if t.starts_with("==") || t.starts_with("Target:") || t.starts_with("Type:") {
            other_lines.push(line);
            continue;
        }
        let tl = t.to_uppercase();
        if tl.contains("CRITICAL") {
            critical.push(t.to_string());
        } else if tl.contains("HIGH") {
            high.push(t.to_string());
        } else if tl.contains("MEDIUM") {
            medium_count += 1;
        } else if tl.contains("LOW") || tl.contains("NEGLIGIBLE") || tl.contains("UNKNOWN") {
            low_count += 1;
        }
    }

    if critical.is_empty() && high.is_empty() && medium_count == 0 && low_count == 0 {
        // No CVEs found
        let target = other_lines
            .iter()
            .find(|l| l.contains("Target:"))
            .copied()
            .unwrap_or("");
        return format!(
            "trivy: no vulnerabilities found{}",
            if target.is_empty() {
                String::new()
            } else {
                format!(" ({target})")
            }
        );
    }

    let mut result: Vec<String> = Vec::new();

    // Target / image context
    for l in &other_lines {
        result.push(l.to_string());
    }

    // Summary line
    let mut parts: Vec<String> = Vec::new();
    if !critical.is_empty() {
        parts.push(format!("{} CRITICAL", critical.len()));
    }
    if !high.is_empty() {
        parts.push(format!("{} HIGH", high.len()));
    }
    if medium_count > 0 {
        parts.push(format!("{medium_count} MEDIUM"));
    }
    if low_count > 0 {
        parts.push(format!("{low_count} LOW/UNKNOWN (suppressed)"));
    }
    result.push(parts.join(", "));

    // Show CRITICAL verbatim, HIGH verbatim, suppress MEDIUM+
    result.extend(critical);
    result.extend(high);
    if medium_count > 0 {
        result.push(format!(
            "… {medium_count} MEDIUM vulnerabilities (use trivy --severity MEDIUM to view)"
        ));
    }

    result.join("\n")
}

fn compress_trivy_json(raw: &str) -> String {
    use once_cell::sync::Lazy;
    use regex::Regex;
    static VUL_RE: Lazy<Regex> = Lazy::new(|| {
        Regex::new(r#""Severity"\s*:\s*"(CRITICAL|HIGH|MEDIUM|LOW|UNKNOWN)""#).unwrap()
    });
    static PKG_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#""PkgName"\s*:\s*"([^"]+)""#).unwrap());
    static CVE_RE: Lazy<Regex> =
        Lazy::new(|| Regex::new(r#""VulnerabilityID"\s*:\s*"(CVE-[^"]+)""#).unwrap());

    let mut counts: std::collections::HashMap<&str, usize> = std::collections::HashMap::new();
    for cap in VUL_RE.captures_iter(raw) {
        *counts
            .entry(cap.get(1).map(|m| m.as_str()).unwrap_or("UNKNOWN"))
            .or_insert(0) += 1;
    }

    if counts.is_empty() {
        return "trivy [json]: no vulnerabilities found".to_string();
    }

    // For CRITICAL/HIGH: extract package + CVE pairs
    let pkgs: Vec<&str> = PKG_RE
        .captures_iter(raw)
        .filter_map(|c| c.get(1).map(|m| m.as_str()))
        .take(10)
        .collect();
    let cves: Vec<&str> = CVE_RE
        .captures_iter(raw)
        .filter_map(|c| c.get(1).map(|m| m.as_str()))
        .take(10)
        .collect();

    let total: usize = counts.values().sum();
    let summary = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN"]
        .iter()
        .filter_map(|s| counts.get(s).map(|c| format!("{c} {s}")))
        .collect::<Vec<_>>()
        .join(", ");

    let pairs: Vec<String> = pkgs
        .iter()
        .zip(cves.iter())
        .map(|(p, c)| format!("{p} ({c})"))
        .collect();

    format!(
        "trivy [json]: {total} vulnerabilities — {summary}\n{}{}",
        pairs.join(", "),
        if total > 10 {
            format!("\n… {} more", total - 10)
        } else {
            String::new()
        }
    )
}

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

    #[test]
    fn strips_progress_lines() {
        let raw = "Downloading vulnerability database...\nUpdating vulnerability database...\nCRITICAL  CVE-2024-1234  openssl  1.0\n";
        let out = compress_trivy("image", raw);
        assert!(!out.contains("Downloading"), "{out}");
        assert!(out.contains("CRITICAL"), "{out}");
    }

    #[test]
    fn suppresses_medium_low() {
        let raw = "CRITICAL  CVE-2024-0001  libssl  bad\nHIGH  CVE-2024-0002  zlib  bad\nMEDIUM  CVE-2024-0003  curl  ok\nMEDIUM  CVE-2024-0004  curl  ok\nLOW  CVE-2024-0005  gzip  ok\n";
        let out = compress_trivy("image", raw);
        assert!(out.contains("CRITICAL"), "{out}");
        assert!(out.contains("HIGH"), "{out}");
        assert!(!out.contains("CVE-2024-0003"), "{out}"); // MEDIUM suppressed
        assert!(out.contains("2 MEDIUM") || out.contains("MEDIUM"), "{out}");
    }

    #[test]
    fn no_cves_clean_message() {
        let raw = "Target: nginx:latest\nType: debian\nTotal: 0 (HIGH: 0, CRITICAL: 0)\n";
        let out = compress_trivy("image", raw);
        assert!(out.contains("no vulnerabilities"), "{out}");
    }

    #[test]
    fn json_mode_extracts_counts() {
        let raw = r#"{"Results":[{"Vulnerabilities":[{"VulnerabilityID":"CVE-2024-0001","PkgName":"openssl","Severity":"CRITICAL"},{"VulnerabilityID":"CVE-2024-0002","PkgName":"zlib","Severity":"HIGH"}]}]}"#;
        let out = compress_trivy("image", raw);
        assert!(out.contains("json") || out.contains("CRITICAL"), "{out}");
    }
}
