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

static DOWNLOAD_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^go: (downloading|extracting|finding module|found) [^\n]+\n?").unwrap()
});
static MODULE_CACHE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^go: (added|upgraded|downgraded) [^\n]+\n?").unwrap());
static TEST_PASS_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^--- PASS: [^\n]+\n?").unwrap());
static TEST_SKIP_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^--- SKIP: [^\n]+\n?").unwrap());
static LINK_VERBOSE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^[#/][^\n]+(\.a|\.o|\.so)[^\n]*\n?").unwrap());
// go vet progress: "# pkg/path" package headers with no following error
static VET_PKG_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^# [a-zA-Z0-9/_\-\.]+\n?").unwrap());
// go mod tidy / download noise
static MOD_DOWNLOAD_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^go: (downloading|extracting|finding|fetching) [^\n]+\n?").unwrap()
});
// go generate invocation echo lines
static GENERATE_ECHO_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^//go:generate [^\n]+\n?").unwrap());
// go mod verify hash lines
static HASH_LINE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^[a-f0-9]{64}\s[^\n]+\n?").unwrap());

// ── go build / run ────────────────────────────────────────────────────────────

pub fn compress_build(raw: &str, exit_code: i32) -> String {
    let cleaned = compactor::normalise(raw);
    let s = DOWNLOAD_RE.replace_all(&cleaned, "");
    if exit_code == 0 {
        let s = LINK_VERBOSE_RE.replace_all(&s, "");
        return compactor::collapse_blanks(&s);
    }
    let errors: Vec<&str> = s.lines().filter(|l| !l.is_empty()).collect();
    errors.join("\n")
}

// ── go test ───────────────────────────────────────────────────────────────────

pub fn compress_test(raw: &str, exit_code: i32) -> String {
    let cleaned = compactor::normalise(raw);
    let s = DOWNLOAD_RE.replace_all(&cleaned, "");
    if exit_code == 0 {
        let summary: Vec<&str> = s
            .lines()
            .filter(|l| {
                l.starts_with("ok ")
                    || l.starts_with("FAIL")
                    || l.starts_with("?")
                    || l.contains("coverage:")
            })
            .collect();
        return summary.join("\n");
    }
    let s = TEST_PASS_RE.replace_all(&s, "");
    let s = TEST_SKIP_RE.replace_all(&s, "");
    compactor::collapse_blanks(&s)
}

// ── go mod tidy / download ────────────────────────────────────────────────────

pub fn compress_mod(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Strip per-package download noise, keep added/removed/error lines
    let s = MOD_DOWNLOAD_RE.replace_all(&cleaned, "");
    // Keep: go: added/removed/upgraded, error lines, "all modules verified"
    let kept: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            t.is_empty()
                || t.starts_with("go: added")
                || t.starts_with("go: removed")
                || t.starts_with("go: upgraded")
                || t.starts_with("go: downgraded")
                || t.starts_with("go: requires")
                || t.contains("error")
                || t.contains("all modules verified")
                || t.contains("inconsistent")
        })
        .collect();
    compactor::collapse_blanks(&kept.join("\n"))
}

// ── go mod verify ─────────────────────────────────────────────────────────────

pub fn compress_mod_verify(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Strip SHA hash lines, keep verification result
    let s = HASH_LINE_RE.replace_all(&cleaned, "");
    compactor::collapse_blanks(&s)
}

// ── go vet ────────────────────────────────────────────────────────────────────

pub fn compress_vet(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Collect vet finding lines; group by package if same error appears in many files
    let s = VET_PKG_RE.replace_all(&cleaned, "");

    // Group identical messages across packages
    let mut msg_counts: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
    let mut unique_lines: Vec<String> = Vec::new();

    for line in s.lines() {
        let t = line.trim();
        if t.is_empty() {
            continue;
        }
        // vet finding: "file.go:N:M: message"
        if let Some(colon_pos) = t.find(':') {
            let after = &t[colon_pos + 1..];
            // Extract just the message part (after file:line:col:)
            let msg = after
                .splitn(3, ':')
                .nth(2)
                .unwrap_or(after)
                .trim()
                .to_string();
            if !msg.is_empty() {
                let count = msg_counts.entry(msg.clone()).or_insert(0);
                *count += 1;
                if *count == 1 {
                    unique_lines.push(line.to_string());
                } else if *count == 2 {
                    // Replace the single line with a grouped one
                    if let Some(pos) = unique_lines.iter().position(|l| l.contains(msg.as_str())) {
                        unique_lines[pos] = format!("… {msg} (×{count})");
                    }
                } else {
                    // Update the existing grouped line
                    if let Some(pos) = unique_lines
                        .iter()
                        .position(|l| l.contains(msg.as_str()) && l.contains('×'))
                    {
                        unique_lines[pos] = format!("… {msg} (×{count})");
                    }
                }
                continue;
            }
        }
        unique_lines.push(line.to_string());
    }

    compactor::collapse_blanks(&unique_lines.join("\n"))
}

// ── go generate ───────────────────────────────────────────────────────────────

pub fn compress_generate(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Strip //go:generate echo lines, keep tool output (errors/warnings)
    let s = GENERATE_ECHO_RE.replace_all(&cleaned, "");
    // Keep only non-empty lines; suppress blank-only result
    let lines: Vec<&str> = s.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.is_empty() {
        return "(go generate: completed)".to_string();
    }
    lines.join("\n")
}

// ── go work ───────────────────────────────────────────────────────────────────

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

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

pub fn compress_go(subcmd: &str, raw: &str, exit_code: i32) -> String {
    let sub = subcmd.trim();
    match sub {
        "build" | "run" | "install" => compress_build(raw, exit_code),
        "test" => compress_test(raw, exit_code),
        "vet" => compress_vet(raw),
        "generate" => compress_generate(raw),
        "work" => compress_work(raw),
        "mod" => {
            // go mod tidy / verify / download / graph
            if sub.contains("verify") {
                compress_mod_verify(raw)
            } else {
                compress_mod(raw)
            }
        }
        _ if sub.starts_with("mod") => {
            if sub.contains("verify") {
                compress_mod_verify(raw)
            } else {
                compress_mod(raw)
            }
        }
        _ => {
            let cleaned = compactor::normalise(raw);
            let s = DOWNLOAD_RE.replace_all(&cleaned, "");
            let s = MODULE_CACHE_RE.replace_all(&s, "");
            compactor::collapse_blanks(&s)
        }
    }
}

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

    #[test]
    fn build_strips_download_lines() {
        let raw =
            "go: downloading github.com/foo/bar v1.2.3\ngo: extracting github.com/foo/bar v1.2.3\n";
        let out = compress_build(raw, 0);
        assert!(!out.contains("downloading"), "{out}");
    }

    #[test]
    fn test_success_keeps_ok_lines() {
        let raw =
            "--- PASS: TestFoo (0.01s)\n--- PASS: TestBar (0.02s)\nok      github.com/foo/pkg  0.034s\n";
        let out = compress_test(raw, 0);
        assert!(out.contains("ok"), "{out}");
        assert!(!out.contains("PASS: TestFoo"), "{out}");
    }

    #[test]
    fn test_failure_keeps_fail_lines() {
        let raw = "--- PASS: TestFoo (0.01s)\n--- FAIL: TestBar (0.02s)\n\tmain_test.go:42: expected 1, got 2\nFAIL\n";
        let out = compress_test(raw, 1);
        assert!(out.contains("FAIL"), "{out}");
        assert!(!out.contains("PASS: TestFoo"), "{out}");
    }

    #[test]
    fn mod_strips_downloading() {
        let raw =
            "go: downloading golang.org/x/sys v0.0.0-20230101\ngo: added golang.org/x/sys v0.0.0\n";
        let out = compress_mod(raw);
        assert!(!out.contains("downloading"), "{out}");
        assert!(out.contains("added"), "{out}");
    }

    #[test]
    fn mod_keeps_removed_lines() {
        let raw = "go: downloading old/pkg v1.0.0\ngo: removed old/pkg v1.0.0\ngo: added new/pkg v2.0.0\n";
        let out = compress_mod(raw);
        assert!(out.contains("removed"), "{out}");
        assert!(out.contains("added"), "{out}");
        assert!(!out.contains("downloading"), "{out}");
    }

    #[test]
    fn vet_strips_package_headers() {
        let raw = "# github.com/foo/bar\n./bar.go:10:5: suspicious argument in Printf call\n# github.com/foo/baz\n";
        let out = compress_vet(raw);
        assert!(out.contains("Printf"), "{out}");
        assert!(!out.contains("# github.com/foo/bar"), "{out}");
    }

    #[test]
    fn generate_strips_invocation_lines() {
        let raw = "//go:generate stringer -type=Color\n//go:generate mockgen -source=foo.go\nfoo.go:10: undefined: Color\n";
        let out = compress_generate(raw);
        assert!(!out.contains("go:generate"), "{out}");
        assert!(out.contains("undefined"), "{out}");
    }

    #[test]
    fn generate_no_output_shows_completed() {
        let raw = "//go:generate stringer -type=Foo\n";
        let out = compress_generate(raw);
        assert!(out.contains("completed"), "{out}");
    }

    #[test]
    fn dispatcher_routes_test() {
        let raw = "ok  github.com/example/pkg  0.01s\n";
        let out = compress_go("test", raw, 0);
        assert!(out.contains("ok"));
    }
}
