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

static PULL_LAYER_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^[a-f0-9]+: (Pulling from|Pull complete|Already exists|Waiting|Downloading|Extracting|Verifying Checksum)[^\n]*\n?").unwrap()
});
static BUILD_STEP_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Step \d+/\d+ : [^\n]+\n?(?: ---> [a-f0-9]+\n?)?").unwrap());
static BUILD_CONTEXT_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Sending build context to Docker daemon[^\n]+\n?").unwrap());
static COMPOSE_PULLING_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\[.+\] Pulling [^\n]+\n?").unwrap());

// ── docker pull ───────────────────────────────────────────────────────────────

pub fn compress_pull(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = PULL_LAYER_RE.replace_all(&cleaned, "");
    // Keep digest + status lines
    compactor::collapse_blanks(&s)
}

// ── docker build ──────────────────────────────────────────────────────────────

pub fn compress_build(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = BUILD_CONTEXT_RE.replace_all(&cleaned, "");
    let s = BUILD_STEP_RE.replace_all(&s, "");
    let s = PULL_LAYER_RE.replace_all(&s, "");
    // Keep "Successfully built" / error lines
    compactor::collapse_blanks(&s)
}

// ── docker ps / images ────────────────────────────────────────────────────────

pub fn compress_ps(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().collect();
    if lines.len() <= 20 {
        return cleaned;
    }
    // Keep header + first 20 rows
    format!(
        "{}\n... [{} more containers] ...",
        lines[..20].join("\n"),
        lines.len() - 20
    )
}

// ── docker compose up ─────────────────────────────────────────────────────────

pub fn compress_compose(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = COMPOSE_PULLING_RE.replace_all(&cleaned, "");
    let s = PULL_LAYER_RE.replace_all(&s, "");
    compactor::collapse_blanks(&s)
}

// ── docker logs ───────────────────────────────────────────────────────────────

pub fn compress_logs(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    super::super::sys::log_dedup(&cleaned)
}

// ── docker inspect ────────────────────────────────────────────────────────────

pub fn compress_inspect(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Strip verbose fields rarely needed: Mounts, Env (long), GraphDriver internals
    let noisy: &[&str] = &[
        "\"GraphDriver\"",
        "\"MountLabel\"",
        "\"ProcessLabel\"",
        "\"AppArmorProfile\"",
        "\"ExecIDs\"",
        "\"HostsPath\"",
        "\"ResolvConfPath\"",
        "\"LogPath\"",
        "\"MountPoint\"",
    ];
    let out: Vec<&str> = cleaned
        .lines()
        .filter(|l| noisy.iter().all(|n| !l.contains(n)))
        .collect();
    let lines = out;
    if lines.len() <= 60 {
        return lines.join("\n");
    }
    format!(
        "{}\n... [{} more lines] ...",
        lines[..60].join("\n"),
        lines.len() - 60
    )
}

// ── docker stats (one-shot, not streaming) ────────────────────────────────────

pub fn compress_stats(raw: &str) -> String {
    // stats --no-stream output is already a compact table — just normalise + truncate
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().collect();
    if lines.len() <= 25 {
        return cleaned;
    }
    format!(
        "{}\n... [{} more containers] ...",
        lines[..25].join("\n"),
        lines.len() - 25
    )
}

// ── docker network ────────────────────────────────────────────────────────────

pub fn compress_network(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // network inspect: strip Options, Labels — keep Name, Driver, Containers
    let noisy: &[&str] = &[
        "\"Options\":",
        "\"Labels\":",
        "\"Internal\":",
        "\"Attachable\":",
        "\"Ingress\":",
        "\"ConfigFrom\":",
        "\"ConfigOnly\":",
        "\"Peers\":",
        "\"Services\":",
    ];
    let out: Vec<&str> = cleaned
        .lines()
        .filter(|l| noisy.iter().all(|n| !l.trim().starts_with(n)))
        .collect();
    out.join("\n")
}

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

pub fn compress_docker(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.starts_with("pull") {
        return compress_pull(raw);
    }
    if sub.starts_with("build") {
        return compress_build(raw);
    }
    if sub.starts_with("ps") || sub.starts_with("images") {
        return compress_ps(raw);
    }
    if sub.starts_with("compose") || sub.starts_with("up") || sub.starts_with("down") {
        return compress_compose(raw);
    }
    if sub.starts_with("logs") {
        return compress_logs(raw);
    }
    if sub.starts_with("inspect") {
        return compress_inspect(raw);
    }
    if sub.starts_with("stats") {
        return compress_stats(raw);
    }
    if sub.starts_with("network") {
        return compress_network(raw);
    }
    if sub.starts_with("system") {
        return compress_system(raw);
    }
    compactor::normalise(raw)
}

// ── docker system df / prune ──────────────────────────────────────────────────

pub fn compress_system(raw: &str) -> String {
    // system df is already compact; system prune shows deleted IDs — truncate
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().filter(|l| !l.trim().is_empty()).collect();
    // Prune output: many SHA lines followed by "Total reclaimed space: X"
    let has_summary = lines
        .iter()
        .any(|l| l.contains("reclaimed") || l.contains("Total"));
    if has_summary && lines.len() > 10 {
        let summary: Vec<&str> = lines
            .iter()
            .copied()
            .filter(|l| l.contains("reclaimed") || l.contains("Total") || l.starts_with("TYPE"))
            .collect();
        let deleted_count = lines
            .iter()
            .filter(|l| l.len() == 12 || l.len() == 64)
            .count();
        if deleted_count > 0 {
            return format!("[{deleted_count} IDs deleted]\n{}", summary.join("\n"));
        }
        return summary.join("\n");
    }
    if lines.len() <= 20 {
        return lines.join("\n");
    }
    format!(
        "{}\n... [{} more lines] ...",
        lines[..20].join("\n"),
        lines.len() - 20
    )
}

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

    #[test]
    fn pull_strips_layer_progress() {
        let raw = "abc123: Pulling from library/ubuntu\nabc123: Pull complete\nStatus: Downloaded newer image\n";
        let out = compress_pull(raw);
        assert!(!out.contains("Pull complete"), "{out}");
        assert!(out.contains("Downloaded newer image"));
    }

    #[test]
    fn build_strips_step_noise() {
        let raw = "Sending build context to Docker daemon  5.12kB\nStep 1/3 : FROM ubuntu:22.04\n ---> abc123\nSuccessfully built def456\n";
        let out = compress_build(raw);
        assert!(!out.contains("Sending build context"), "{out}");
        assert!(!out.contains("Step 1/3"), "{out}");
        assert!(out.contains("Successfully built"));
    }

    #[test]
    fn ps_truncates_many_containers() {
        let header = "CONTAINER ID   IMAGE   COMMAND   STATUS\n";
        let rows: String = (0..25)
            .map(|i| format!("{:012x}   img{i}   cmd   Up\n", i))
            .collect();
        let out = compress_ps(&format!("{header}{rows}"));
        assert!(out.contains("more containers"), "{out}");
    }

    #[test]
    fn inspect_strips_noisy_fields() {
        let raw = "[\n  {\n    \"Id\": \"abc123\",\n    \"GraphDriver\": { \"Data\": null },\n    \"MountLabel\": \"\",\n    \"State\": { \"Status\": \"running\" }\n  }\n]\n";
        let out = compress_inspect(raw);
        assert!(!out.contains("GraphDriver"), "{out}");
        assert!(out.contains("running"), "{out}");
    }

    #[test]
    fn network_strips_options_labels() {
        let raw = "[\n  {\n    \"Name\": \"bridge\",\n    \"Driver\": \"bridge\",\n    \"Options\": {},\n    \"Labels\": {},\n    \"Containers\": {}\n  }\n]\n";
        let out = compress_network(raw);
        assert!(!out.contains("\"Options\":"), "{out}");
        assert!(out.contains("\"Name\""), "{out}");
    }
}
