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

// "Running with dbt=1.7.0" / "Found 42 models, 18 tests, ..." preamble
static PREAMBLE_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^(?:Running with dbt|Found \d+ models|Concurrency)[^\n]*\n?").unwrap()
});
// timestamp prefix: "12:34:56  "
static TIMESTAMP_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\d{2}:\d{2}:\d{2}\s+").unwrap());
// "Completed with \d warnings" trailing lines
static WARN_TRAILER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Completed with \d+ warnings?\.[^\n]*\n?").unwrap());

// ── dbt run / build ───────────────────────────────────────────────────────────

pub fn compress_run(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = PREAMBLE_RE.replace_all(&cleaned, "");
    let s = TIMESTAMP_RE.replace_all(&s, "");
    let s = WARN_TRAILER_RE.replace_all(&s, "");

    let mut ok_count = 0usize;
    let mut failed: Vec<String> = Vec::new();
    let mut warn: Vec<String> = Vec::new();
    let mut summary: Option<String> = None;

    for line in s.lines() {
        let t = line.trim();
        if t.is_empty() {
            continue;
        }
        if t.contains("Completed successfully") || t.starts_with("Done.") {
            summary = Some(t.to_string());
        } else if t.contains("ERROR") || t.contains("FAIL") {
            failed.push(t.to_string());
        } else if t.contains("WARN") {
            warn.push(t.to_string());
        } else if t.contains("OK") || t.contains("PASS") {
            ok_count += 1;
        }
    }

    let mut out: Vec<String> = Vec::new();
    if ok_count > 0 {
        out.push(format!("  ✔  {} models OK", ok_count));
    }
    out.extend(warn.iter().cloned());
    out.extend(failed.iter().cloned());
    if let Some(s) = summary {
        out.push(s);
    }
    if out.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    out.join("\n")
}

// ── dbt test ──────────────────────────────────────────────────────────────────

pub fn compress_test(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = PREAMBLE_RE.replace_all(&cleaned, "");
    let s = TIMESTAMP_RE.replace_all(&s, "");

    let mut pass_count = 0usize;
    let mut failed: Vec<String> = Vec::new();
    let mut summary: Option<String> = None;

    for line in s.lines() {
        let t = line.trim();
        if t.is_empty() {
            continue;
        }
        if t.contains("Finished running") || t.contains("Done.") {
            summary = Some(t.to_string());
        } else if t.contains("FAIL") || t.contains("ERROR") {
            failed.push(t.to_string());
        } else if t.contains("PASS") || t.contains("OK") {
            pass_count += 1;
        }
    }

    let mut out: Vec<String> = Vec::new();
    if pass_count > 0 {
        out.push(format!("  ✔  {} tests passed", pass_count));
    }
    out.extend(failed.iter().cloned());
    if let Some(s) = summary {
        out.push(s);
    }
    if out.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    out.join("\n")
}

// ── dbt compile / docs ────────────────────────────────────────────────────────

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

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

pub fn compress_dbt(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.starts_with("run") || sub.starts_with("build") {
        return compress_run(raw);
    }
    if sub.starts_with("test") {
        return compress_test(raw);
    }
    compress_generic(raw)
}

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

    #[test]
    fn run_strips_preamble_and_groups_ok() {
        let raw = "Running with dbt=1.7.0\nFound 5 models, 3 tests, 0 snapshots, 0 analyses, 546 macros, 0 operations, 0 seed files, 0 sources\nConcurrency: 4 threads (target='dev')\n\n12:34:56  1 of 5 START sql table model dbt_dev.stg_orders .......................... [RUN]\n12:34:57  1 of 5 OK created sql table model dbt_dev.stg_orders ..................... [OK in 1.23s]\n12:34:57  2 of 5 START sql table model dbt_dev.stg_customers ........................ [RUN]\n12:34:58  2 of 5 OK created sql table model dbt_dev.stg_customers ................... [OK in 0.89s]\n12:34:58  3 of 5 ERROR creating sql table model dbt_dev.bad_model ................... [ERROR in 0.12s]\n\n12:34:58  Finished running 2 table models, 0 view models in 0 hours 0 minutes and 2.34 seconds (2.34s).\n\nCompleted with 1 error and 0 warnings:\n\nDone. PASS=2 WARN=0 ERROR=1 SKIP=0 TOTAL=5\n";
        let out = compress_run(raw);
        assert!(!out.contains("Running with dbt"), "{out}");
        assert!(!out.contains("Found 5 models"), "{out}");
        assert!(!out.contains("12:34:56"), "{out}");
        assert!(out.contains("OK") || out.contains("✔"), "{out}");
        assert!(out.contains("ERROR"), "{out}");
    }

    #[test]
    fn test_groups_pass_and_shows_failures() {
        let raw = "Running with dbt=1.7.0\n12:00:01  1 of 3 START test not_null_orders_id ...................................... [RUN]\n12:00:01  1 of 3 PASS not_null_orders_id ............................................ [PASS in 0.05s]\n12:00:01  2 of 3 START test unique_orders_id ......................................... [RUN]\n12:00:01  2 of 3 PASS unique_orders_id ............................................... [PASS in 0.04s]\n12:00:01  3 of 3 START test not_null_customers_name .................................. [RUN]\n12:00:02  3 of 3 FAIL not_null_customers_name ........................................ [FAIL 3]\n\nDone. PASS=2 WARN=0 ERROR=0 FAIL=1 SKIP=0 TOTAL=3\n";
        let out = compress_test(raw);
        assert!(!out.contains("Running with dbt"), "{out}");
        assert!(out.contains("passed") || out.contains("✔"), "{out}");
        assert!(out.contains("FAIL"), "{out}");
    }
}
