//! End-to-end: spawn `koala-core workflow enforce` against a fixture
//! git repo and confirm the gate decisions match the contract.

use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;

fn binary_path() -> &'static str {
    env!("CARGO_BIN_EXE_koala-core")
}

fn git(args: &[&str], cwd: &Path) -> std::process::Output {
    Command::new("git")
        .args(args)
        .current_dir(cwd)
        .output()
        .expect("git invocation")
}

fn init_repo(root: &Path) {
    git(&["init", "--initial-branch=main"], root);
    git(&["config", "user.email", "test@example.com"], root);
    git(&["config", "user.name", "Test"], root);
    git(&["config", "commit.gpgsign", "false"], root);
}

fn commit_all(root: &Path, msg: &str) {
    git(&["add", "-A"], root);
    git(&["commit", "-m", msg, "--allow-empty"], root);
}

fn write(root: &Path, rel: &str, body: &str) {
    let p = root.join(rel);
    fs::create_dir_all(p.parent().unwrap()).unwrap();
    fs::write(p, body).unwrap();
}

#[test]
fn small_pr_passes_with_no_artifact_no_feature_sync() {
    let tmp = TempDir::new().unwrap();
    init_repo(tmp.path());
    write(tmp.path(), "README.md", "# r\n");
    commit_all(tmp.path(), "init");

    // Small change: a couple lines in one private function (no `pub`,
    // so the public-API check won't promote to large).
    write(tmp.path(), "src/lib.rs", "fn k() -> u32 {\n    42\n}\n");
    commit_all(tmp.path(), "small change");

    let out = Command::new(binary_path())
        .args(["workflow", "enforce", "--base", "main~1"])
        .current_dir(tmp.path())
        .output()
        .expect("run workflow enforce");
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(
        out.status.success(),
        "small PR should fail-open and pass:\n{stdout}"
    );
    assert!(
        stdout.contains("PR size: small"),
        "expected `small` size:\n{stdout}"
    );
    assert!(
        stdout.contains("✓ all required gates satisfied"),
        "expected satisfied banner:\n{stdout}"
    );
}

#[test]
fn medium_pr_without_artifact_blocks() {
    let tmp = TempDir::new().unwrap();
    init_repo(tmp.path());
    // Seed: one feature file already exists; touching it doesn't
    // satisfy artifact gate.
    write(
        tmp.path(),
        "wiki/features/foo.md",
        "---\nid: foo\nstatus: in-progress\n---\n# foo\n",
    );
    write(tmp.path(), "README.md", "# r\n");
    commit_all(tmp.path(), "init");

    // Medium-sized change: ~150 LOC across one crate, single file.
    let big: String = (0..150).map(|i| format!("// line {i}\n")).collect();
    write(tmp.path(), "crates/koala-core/src/lib.rs", &big);
    commit_all(tmp.path(), "medium change");

    let out = Command::new(binary_path())
        .args(["workflow", "enforce", "--base", "main~1"])
        .current_dir(tmp.path())
        .output()
        .expect("run workflow enforce");
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(
        !out.status.success(),
        "medium PR without artifact should block:\n{stdout}"
    );
    assert!(
        stdout.contains("PR size: medium") || stdout.contains("PR size: large"),
        "expected medium/large size for 150-LOC change:\n{stdout}"
    );
    assert!(
        stdout.contains("koala.artifact-required"),
        "expected artifact-required gate to fire:\n{stdout}"
    );
}

#[test]
fn medium_pr_with_artifact_passes() {
    let tmp = TempDir::new().unwrap();
    init_repo(tmp.path());
    write(
        tmp.path(),
        "wiki/features/foo.md",
        "---\nid: foo\nstatus: in-progress\n---\n# foo\n",
    );
    write(tmp.path(), "README.md", "# r\n");
    commit_all(tmp.path(), "init");

    // Medium change + artifact in same PR.
    let big: String = (0..150).map(|i| format!("// line {i}\n")).collect();
    write(tmp.path(), "crates/koala-core/src/lib.rs", &big);
    write(
        tmp.path(),
        ".review/round-1/concept-changes-look-good.md",
        "---\nschema: koala-artifact/v1\n---\n# review\n",
    );
    commit_all(tmp.path(), "medium change with artifact");

    let out = Command::new(binary_path())
        .args(["workflow", "enforce", "--base", "main~1"])
        .current_dir(tmp.path())
        .output()
        .expect("run workflow enforce");
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(
        out.status.success(),
        "medium PR with artifact should pass:\n{stdout}"
    );
    assert!(
        stdout.contains("artifacts:        1 touched"),
        "expected one artifact recorded:\n{stdout}"
    );
}
