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

// "Downloading provider: registry.pulumi.com/..." progress lines
static DOWNLOADING_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Downloading provider[^\n]*\n?").unwrap());
// "Installing provider..." lines
static INSTALLING_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Installing provider[^\n]*\n?").unwrap());
// Spinner/progress lines: lines that start with spinner chars or are "..." progress
static SPINNER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s*[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏][^\n]*\n?").unwrap());
// Pulumi "     Type    Name    Status" separator line (all spaces)
static TYPE_HEADER_BLANK_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s{4,}Type\s+Name\s+Status\s*$").unwrap());
// "Do you want to perform this update?" interactive prompt — strip when piped
static PROMPT_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^Do you want to perform this (update|destroy|preview)\?[^\n]*\n?").unwrap()
});

// ── pulumi preview ────────────────────────────────────────────────────────────

pub fn compress_preview(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = DOWNLOADING_RE.replace_all(&cleaned, "");
    let s = INSTALLING_RE.replace_all(&s, "");
    let s = SPINNER_RE.replace_all(&s, "");
    let s = PROMPT_RE.replace_all(&s, "");
    compactor::collapse_blanks(&s)
}

// ── pulumi up / update ────────────────────────────────────────────────────────

pub fn compress_up(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = DOWNLOADING_RE.replace_all(&cleaned, "");
    let s = INSTALLING_RE.replace_all(&s, "");
    let s = SPINNER_RE.replace_all(&s, "");
    let s = PROMPT_RE.replace_all(&s, "");
    let s = TYPE_HEADER_BLANK_RE.replace_all(&s, "");

    // During update, strip lines that are pure "still working" relay:
    // lines like "     pulumi:pulumi:Stack  mystack  updating..." without error
    let out: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.ends_with("updating...")
                && !t.ends_with("creating...")
                && !t.ends_with("deleting...")
        })
        .collect();

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

// ── pulumi destroy ────────────────────────────────────────────────────────────

pub fn compress_destroy(raw: &str) -> String {
    // Same treatment as up — just different verb in status lines
    compress_up(raw)
}

// ── pulumi stack ──────────────────────────────────────────────────────────────

pub fn compress_stack(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().filter(|l| !l.trim().is_empty()).collect();
    // stack ls output: keep table rows, truncate if huge
    if lines.len() > 30 {
        return format!(
            "{}\n… [{} more stacks] …",
            lines[..30].join("\n"),
            lines.len() - 30
        );
    }
    lines.join("\n")
}

// ── pulumi logs ───────────────────────────────────────────────────────────────

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

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

pub fn compress_pulumi(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.starts_with("preview") || sub.starts_with("pre") {
        return compress_preview(raw);
    }
    if sub.starts_with("up") || sub.starts_with("update") {
        return compress_up(raw);
    }
    if sub.starts_with("destroy") || sub.starts_with("down") {
        return compress_destroy(raw);
    }
    if sub.starts_with("stack") {
        return compress_stack(raw);
    }
    if sub.starts_with("logs") {
        return compress_logs(raw);
    }
    // refresh / config / import / etc.
    compactor::normalise(raw)
}

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

    #[test]
    fn preview_strips_downloading_provider() {
        let raw = "Downloading provider: registry.pulumi.com/pulumi/aws@6.0.0\nInstalling provider pulumi-aws\nPreviewing update (dev):\n\n     Type                 Name      Plan\n +   pulumi:pulumi:Stack  myapp-dev  create\n\nResources:\n    + 3 to create\n";
        let out = compress_preview(raw);
        assert!(!out.contains("Downloading provider"), "{out}");
        assert!(!out.contains("Installing provider"), "{out}");
        assert!(out.contains("Resources:"), "{out}");
        assert!(out.contains("3 to create"), "{out}");
    }

    #[test]
    fn up_strips_updating_relay_lines() {
        let raw = "Updating (dev):\n\n     Type                 Name       Status\n +   pulumi:pulumi:Stack  myapp-dev  updating...\n +   aws:s3:Bucket        my-bucket  creating...\n +   aws:s3:Bucket        my-bucket  created\n\nOutputs:\n    bucketName: \"my-bucket-abc\"\n\nResources:\n    + 2 created\n\nDuration: 8s\n";
        let out = compress_up(raw);
        assert!(!out.contains("updating..."), "{out}");
        assert!(!out.contains("creating..."), "{out}");
        assert!(out.contains("created"), "{out}");
        assert!(out.contains("Outputs:"), "{out}");
        assert!(out.contains("Duration:"), "{out}");
    }

    #[test]
    fn up_strips_interactive_prompt() {
        let raw = "Previewing update (dev):\n\nResources:\n    + 1 to create\n\nDo you want to perform this update? yes\nUpdating (dev):\n";
        let out = compress_up(raw);
        assert!(
            !out.contains("Do you want to perform this update?"),
            "{out}"
        );
        assert!(out.contains("Resources:"), "{out}");
    }

    #[test]
    fn destroy_keeps_deleted_summary() {
        let raw = "Destroying (dev):\n\n     Type                 Name       Status\n -   pulumi:pulumi:Stack  myapp-dev  deleting...\n -   aws:s3:Bucket        my-bucket  deleted\n\nResources:\n    - 2 deleted\n\nDuration: 4s\n";
        let out = compress_destroy(raw);
        assert!(!out.contains("deleting..."), "{out}");
        assert!(out.contains("- 2 deleted"), "{out}");
        assert!(out.contains("Duration:"), "{out}");
    }

    #[test]
    fn stack_truncates_large_list() {
        let rows: Vec<String> = (0..35)
            .map(|i| format!("stack-{i}  aws  https://app.pulumi.com/org/proj/stack-{i}"))
            .collect();
        let out = compress_stack(&rows.join("\n"));
        assert!(out.contains("more stacks"), "{out}");
    }
}
