use std::fs;
use std::process::Command;

fn srcwalk() -> Command {
    Command::new(env!("CARGO_BIN_EXE_srcwalk"))
}

fn temp_dir(name: &str) -> std::path::PathBuf {
    let dir = std::env::temp_dir().join(format!(
        "srcwalk_{name}_{}_{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    ));
    fs::create_dir_all(&dir).unwrap();
    dir
}

#[test]
fn impact_labels_name_matched_calls_and_groups_receivers() {
    let dir = temp_dir("impact_receivers");
    fs::write(
        dir.join("sample.ts"),
        r#"
export async function shutdown(conn: Conn, watcher: Watcher) {
  await conn.close();
  watcher.close();
}
"#,
    )
    .unwrap();

    let out = srcwalk()
        .args(["close", "--impact", "--scope"])
        .arg(&dir)
        .output()
        .unwrap();
    let stdout = String::from_utf8_lossy(&out.stdout);
    let stderr = String::from_utf8_lossy(&out.stderr);

    assert!(
        out.status.success(),
        "impact should succeed, stderr:\n{stderr}\nstdout:\n{stdout}"
    );
    assert!(
        stdout.contains("<- name-matched calls from"),
        "expected honest heading, got:\n{stdout}"
    );
    assert!(
        stdout.contains("[group] receiver=conn count=1"),
        "expected conn receiver group, got:\n{stdout}"
    );
    assert!(
        stdout.contains("[group] receiver=watcher count=1"),
        "expected watcher receiver group, got:\n{stdout}"
    );
    assert!(
        stdout.contains("Warning: no definitions found; showing name-matched call sites only"),
        "expected no-definition warning, got:\n{stdout}"
    );
}

#[test]
fn impact_warns_for_broad_name_matched_symbols() {
    let dir = temp_dir("impact_broad");
    let mut body = String::from("export function many(items: any[]) {\n");
    for i in 0..51 {
        body.push_str(&format!("  items[{i}].close();\n"));
    }
    body.push_str("}\n");
    fs::write(dir.join("sample.ts"), body).unwrap();

    let out = srcwalk()
        .args(["close", "--impact", "--scope"])
        .arg(&dir)
        .output()
        .unwrap();
    let stdout = String::from_utf8_lossy(&out.stdout);
    let stderr = String::from_utf8_lossy(&out.stderr);

    assert!(
        out.status.success(),
        "impact should succeed, stderr:\n{stderr}\nstdout:\n{stdout}"
    );
    assert!(
        stdout.contains("... 31 more call sites"),
        "expected capped callsite output, got:\n{stdout}"
    );
    assert!(
        stdout.contains("Warning: broad symbol name; impact is name-matched"),
        "expected broad symbol warning, got:\n{stdout}"
    );
    assert!(
        stdout.contains("51 direct name-matched call sites found; impact output capped"),
        "expected capped footer, got:\n{stdout}"
    );
    assert!(
        stdout.contains("direct-name scope scan"),
        "expected scan caveat, got:\n{stdout}"
    );
}

#[test]
fn impact_reports_zero_callers_as_not_proof_of_no_runtime_callers() {
    let dir = temp_dir("impact_zero");
    fs::write(
        dir.join("sample.ts"),
        r#"
export function uniqueTarget() {
  return 1;
}
"#,
    )
    .unwrap();

    let out = srcwalk()
        .args(["uniqueTarget", "--impact", "--scope"])
        .arg(&dir)
        .output()
        .unwrap();
    let stdout = String::from_utf8_lossy(&out.stdout);
    let stderr = String::from_utf8_lossy(&out.stderr);

    assert!(
        out.status.success(),
        "impact should succeed, stderr:
{stderr}
stdout:
{stdout}"
    );
    assert!(
        stdout.contains("0 direct name-matched call sites found"),
        "expected zero-call footer, got:
{stdout}"
    );
    assert!(
        stdout.contains("not proof of no runtime callers"),
        "expected zero-call caveat, got:
{stdout}"
    );
}
