//! Bound-plan staleness: `pic plan` records a state token under `.picloud/`,
//! and a later `pic apply` refuses (without `--force`) if the app changed
//! out-of-band since the plan was reviewed.

use std::fs;

use tempfile::TempDir;

use crate::common;
use crate::common::cleanup::AppGuard;

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn apply_refuses_when_state_moved_since_plan() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let slug = common::unique_slug("stale");
    common::pic_as(&env)
        .args(["apps", "create", &slug])
        .assert()
        .success();
    let _guard = AppGuard::new(&env.url, &env.token, &slug);

    let dir = TempDir::new().unwrap();
    fs::create_dir_all(dir.path().join("scripts")).unwrap();
    fs::write(dir.path().join("scripts/hello.rhai"), "let x = 1; x").unwrap();
    let manifest_path = dir.path().join("picloud.toml");
    let manifest = format!(
        "[app]\nslug = \"{slug}\"\nname = \"Stale\"\n\n\
         [[scripts]]\nname = \"hello\"\nfile = \"scripts/hello.rhai\"\n"
    );
    fs::write(&manifest_path, &manifest).unwrap();

    // Establish hello, then plan — the plan records the current state token.
    apply(&env, &manifest_path).assert().success();
    common::pic_as(&env)
        .args(["plan", "--file"])
        .arg(&manifest_path)
        .assert()
        .success();
    assert!(
        dir.path().join(".picloud/plan.json").exists(),
        "plan must record the bound-plan token under .picloud/"
    );

    // Out-of-band change: deploy an extra script the manifest doesn't mention.
    fs::write(dir.path().join("scripts/sneaky.rhai"), "let y = 2; y").unwrap();
    common::pic_as(&env)
        .args(["scripts", "deploy"])
        .arg(dir.path().join("scripts/sneaky.rhai"))
        .args(["--app", &slug])
        .assert()
        .success();

    // Apply must now refuse — the live state no longer matches the plan.
    let refused = apply(&env, &manifest_path).output().expect("apply");
    assert!(
        !refused.status.success(),
        "apply must refuse after out-of-band change"
    );
    let err = String::from_utf8_lossy(&refused.stderr);
    assert!(
        err.contains("changed since") || err.contains("pic plan"),
        "refusal should explain the staleness:\n{err}"
    );

    // `--force` bypasses the check and applies anyway.
    common::pic_as(&env)
        .args(["apply", "--file"])
        .arg(&manifest_path)
        .arg("--force")
        .assert()
        .success();
}

fn apply(env: &common::TestEnv, manifest_path: &std::path::Path) -> assert_cmd::Command {
    let mut cmd = common::pic_as(env);
    cmd.args(["apply", "--file"]).arg(manifest_path);
    cmd
}
