//! `koala-core workflow enforce --from-gh-pr <N>` resolves human
//! review status by shelling out to `gh`. Tests stub `gh` via the
//! `KOALA_GH_BIN` env var so no GitHub auth or network is required.

#![cfg(unix)]

use std::fs;
use std::os::unix::fs::PermissionsExt;
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) {
    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();
}

/// Drop a stub `gh` script into `dir` that prints `decision` and
/// returns its absolute path. Mode 0o755 so it's executable.
fn stub_gh(dir: &Path, decision: &str) -> std::path::PathBuf {
    let path = dir.join("gh-stub");
    let body = format!("#!/bin/sh\nprintf '{decision}\\n'\n");
    fs::write(&path, body).unwrap();
    let mut perms = fs::metadata(&path).unwrap().permissions();
    perms.set_mode(0o755);
    fs::set_permissions(&path, perms).unwrap();
    path
}

fn architectural_repo() -> (TempDir, std::path::PathBuf) {
    let tmp = TempDir::new().unwrap();
    init_repo(tmp.path());
    write(tmp.path(), "README.md", "# r\n");
    write(tmp.path(), ".koala/architecture.toml", "# layered\n");
    commit_all(tmp.path(), "init");

    // Architectural change: edit .koala/architecture.toml + add an ADR.
    write(
        tmp.path(),
        ".koala/architecture.toml",
        "# layered v2\n[layers]\ncore = []\n",
    );
    write(
        tmp.path(),
        "wiki/decisions/0042-rewrite.md",
        "---\nid: 0042\ntitle: Rewrite\nstatus: accepted\ndate: 2026-05-08\n---\n# ADR\n",
    );
    write(
        tmp.path(),
        ".review/round-1/concept-architectural-change.md",
        "---\nschema: koala-artifact/v1\n---\n# review\n",
    );
    write(
        tmp.path(),
        "wiki/features/foo.md",
        "---\nid: foo\nstatus: in-progress\n---\n# foo\n",
    );
    commit_all(tmp.path(), "architectural");
    let path = tmp.path().to_path_buf();
    (tmp, path)
}

fn run_enforce(repo: &Path, gh_stub: &Path, pr: &str) -> std::process::Output {
    Command::new(binary_path())
        .args([
            "workflow",
            "enforce",
            "--base",
            "main~1",
            "--from-gh-pr",
            pr,
        ])
        .current_dir(repo)
        .env("KOALA_GH_BIN", gh_stub)
        .output()
        .expect("run workflow enforce")
}

#[test]
fn approved_pr_passes_architectural_gate() {
    let (_tmp, repo) = architectural_repo();
    let stubs = TempDir::new().unwrap();
    let gh = stub_gh(stubs.path(), "APPROVED");

    let out = run_enforce(&repo, &gh, "42");
    let stdout = String::from_utf8_lossy(&out.stdout);

    assert!(
        stdout.contains("PR size: architectural"),
        "expected architectural classification:\n{stdout}"
    );
    assert!(
        stdout.contains("(gh PR #42: APPROVED)"),
        "expected gh provenance line:\n{stdout}"
    );
    assert!(
        out.status.success(),
        "APPROVED architectural PR should pass:\n{stdout}"
    );
}

#[test]
fn pending_pr_blocks_architectural_gate() {
    let (_tmp, repo) = architectural_repo();
    let stubs = TempDir::new().unwrap();
    let gh = stub_gh(stubs.path(), "REVIEW_REQUIRED");

    let out = run_enforce(&repo, &gh, "42");
    let stdout = String::from_utf8_lossy(&out.stdout);

    assert!(
        !out.status.success(),
        "REVIEW_REQUIRED architectural PR should block:\n{stdout}"
    );
    assert!(
        stdout.contains("koala.human-review"),
        "expected human-review gate to fire:\n{stdout}"
    );
    assert!(
        stdout.contains("(gh PR #42: REVIEW_REQUIRED)"),
        "expected gh provenance to surface decision:\n{stdout}"
    );
}

#[test]
fn null_decision_treated_as_pending() {
    let (_tmp, repo) = architectural_repo();
    let stubs = TempDir::new().unwrap();
    // gh emits the literal "null" when no review has been requested.
    let gh = stub_gh(stubs.path(), "null");

    let out = run_enforce(&repo, &gh, "1");
    let stdout = String::from_utf8_lossy(&out.stdout);

    assert!(!out.status.success());
    assert!(
        stdout.contains("PENDING"),
        "null decision should be displayed as PENDING:\n{stdout}"
    );
}
