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

// timestamp prefix: time="2024-01-01T00:00:00Z"
static TIMESTAMP_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?m)\btime="[^"]+"\s+"#).unwrap());
// health polling: "Waiting for app to become healthy..."
static POLLING_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^(?:Waiting for|Retrying|Still waiting)[^\n]*\n?").unwrap());
// table border lines
static BORDER_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^[+\-=]+\n?").unwrap());

// ── argocd app sync ───────────────────────────────────────────────────────────

pub fn compress_sync(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = TIMESTAMP_RE.replace_all(&cleaned, "");
    let s = POLLING_RE.replace_all(&s, "");
    // Keep: resource lines, status, errors, sync result
    let useful: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && (t.contains("Synced")
                    || t.contains("OutOfSync")
                    || t.contains("Healthy")
                    || t.contains("Degraded")
                    || t.contains("Progressing")
                    || t.contains("Missing")
                    || t.contains("error")
                    || t.contains("Error")
                    || t.contains("FAILED")
                    || t.contains("successfully")
                    || t.starts_with("GROUP")
                    || t.starts_with("apps/")
                    || t.starts_with("deployment")
                    || t.starts_with("service")
                    || t.starts_with("configmap")
                    || t.starts_with("secret"))
        })
        .collect();
    if useful.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    useful.join("\n")
}

// ── argocd app list ───────────────────────────────────────────────────────────

pub fn compress_list(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = BORDER_RE.replace_all(&cleaned, "");
    // Keep header + data rows; truncate at 30
    let lines: Vec<&str> = s.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.len() > 30 {
        return format!(
            "{}\n… [{} more apps]",
            lines[..30].join("\n"),
            lines.len() - 30
        );
    }
    lines.join("\n")
}

// ── argocd app diff ───────────────────────────────────────────────────────────

pub fn compress_diff(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty() && (t.starts_with('+') || t.starts_with('-') || t.starts_with("@@"))
        })
        .collect();
    if lines.is_empty() {
        return "No diff (resources are in sync)".to_string();
    }
    if lines.len() > 60 {
        return format!(
            "{}\n… [{} more diff lines]",
            lines[..60].join("\n"),
            lines.len() - 60
        );
    }
    lines.join("\n")
}

// ── argocd app status / get ───────────────────────────────────────────────────

pub fn compress_status(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = TIMESTAMP_RE.replace_all(&cleaned, "");
    // Keep key-value summary fields
    let useful: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && (t.starts_with("Name:")
                    || t.starts_with("Server:")
                    || t.starts_with("Namespace:")
                    || t.starts_with("Sync")
                    || t.starts_with("Health")
                    || t.starts_with("Operation")
                    || t.contains("Synced")
                    || t.contains("Healthy")
                    || t.contains("Degraded")
                    || t.contains("OutOfSync")
                    || t.contains("error")
                    || t.contains("Error"))
        })
        .collect();
    if useful.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    useful.join("\n")
}

// ── top-level dispatcher ──────────────────────────────────────────────────────

pub fn compress_argocd(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.contains("sync") {
        return compress_sync(raw);
    }
    if sub.contains("list") {
        return compress_list(raw);
    }
    if sub.contains("diff") {
        return compress_diff(raw);
    }
    if sub.contains("status") || sub.contains("get") {
        return compress_status(raw);
    }
    // install / login / rollout / other
    let cleaned = compactor::normalise(raw);
    let s = TIMESTAMP_RE.replace_all(&cleaned, "");
    let s = POLLING_RE.replace_all(&s, "");
    compactor::collapse_blanks(&s)
}

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

    #[test]
    fn sync_strips_timestamps_and_polling() {
        let raw = "time=\"2024-01-01T00:00:00Z\" msg=\"Syncing app\" app=my-app\nWaiting for app to become healthy...\nWaiting for app to become healthy...\nGROUP  KIND        NAMESPACE  NAME     STATUS  HEALTH\n       Deployment  default    my-app   Synced  Healthy\n";
        let out = compress_sync(raw);
        assert!(!out.contains("time="), "{out}");
        assert!(!out.contains("Waiting for"), "{out}");
        assert!(out.contains("Synced") || out.contains("Healthy"), "{out}");
    }

    #[test]
    fn list_truncates_at_30() {
        let mut lines = vec!["NAME  CLUSTER  NAMESPACE  STATUS  HEALTH".to_string()];
        for i in 0..35 {
            lines.push(format!("app-{i}  cluster  default  Synced  Healthy"));
        }
        let out = compress_list(&lines.join("\n"));
        assert!(out.contains("more apps"), "{out}");
    }

    #[test]
    fn diff_keeps_only_changed_lines() {
        let raw = "===== apps/Deployment default/my-app ======\n  unchanged line\n+ added line\n- removed line\n@@ -1,3 +1,4 @@\n";
        let out = compress_diff(raw);
        assert!(out.contains("+ added line"), "{out}");
        assert!(out.contains("- removed line"), "{out}");
        assert!(!out.contains("unchanged line"), "{out}");
    }

    #[test]
    fn no_diff_returns_in_sync_message() {
        let raw = "no diff\n";
        let out = compress_diff(raw);
        assert!(
            out.contains("in sync") || out.contains("No diff") || out.is_empty(),
            "{out}"
        );
    }
}
