//! M5: `pic apply` creates email + queue triggers. The email trigger's
//! inbound secret is referenced by name (pushed via `pic secret set`) and
//! resolved + re-sealed server-side — never written into the manifest.

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_email_and_queue_triggers() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let slug = common::unique_slug("m5");
    common::pic_as(&env)
        .args(["apps", "create", &slug])
        .assert()
        .success();
    let _guard = AppGuard::new(&env.url, &env.token, &slug);

    // The email trigger references this secret by name; push its value
    // out-of-band first.
    common::pic_as(&env)
        .args(["secrets", "set", "--app", &slug, "email-hmac"])
        .write_stdin("super-secret-hmac")
        .assert()
        .success();

    let dir = TempDir::new().unwrap();
    fs::create_dir_all(dir.path().join("scripts")).unwrap();
    fs::write(dir.path().join("scripts/handler.rhai"), "let x = 1; x").unwrap();
    let manifest = format!(
        "[app]\nslug = \"{slug}\"\nname = \"M5\"\n\n\
         [secrets]\nnames = [\"email-hmac\"]\n\n\
         [[scripts]]\nname = \"handler\"\nfile = \"scripts/handler.rhai\"\n\n\
         [[triggers.queue]]\nscript = \"handler\"\nqueue_name = \"jobs\"\n\n\
         [[triggers.email]]\nscript = \"handler\"\ninbound_secret_ref = \"email-hmac\"\n"
    );
    let manifest_path = dir.path().join("picloud.toml");
    fs::write(&manifest_path, &manifest).unwrap();

    let out = common::pic_as(&env)
        .args(["apply", "--file"])
        .arg(&manifest_path)
        .output()
        .expect("apply");
    assert!(
        out.status.success(),
        "apply failed: {}",
        String::from_utf8_lossy(&out.stderr)
    );

    // Both triggers exist.
    let s = String::from_utf8(
        common::pic_as(&env)
            .args(["triggers", "ls", "--app", &slug])
            .output()
            .unwrap()
            .stdout,
    )
    .unwrap();
    assert!(
        s.lines().any(|l| l.contains("queue")),
        "queue trigger missing:\n{s}"
    );
    assert!(
        s.lines().any(|l| l.contains("email")),
        "email trigger missing:\n{s}"
    );

    // Re-apply is a no-op (both triggers match by identity).
    let r = String::from_utf8(
        common::pic_as(&env)
            .args(["apply", "--file"])
            .arg(&manifest_path)
            .output()
            .unwrap()
            .stdout,
    )
    .unwrap();
    assert!(!r.contains("+1"), "re-apply should be a no-op:\n{r}");
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn prune_refuses_to_orphan_email_trigger() {
    // `pull` can't represent email triggers, so a manifest that omits the
    // script owning one would, under `--prune`, cascade-delete the trigger
    // (and its sealed secret) when the script is dropped. Apply must refuse.
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let slug = common::unique_slug("m5-orphan");
    common::pic_as(&env)
        .args(["apps", "create", &slug])
        .assert()
        .success();
    let _guard = AppGuard::new(&env.url, &env.token, &slug);

    common::pic_as(&env)
        .args(["secrets", "set", "--app", &slug, "email-hmac"])
        .write_stdin("super-secret-hmac")
        .assert()
        .success();

    let dir = TempDir::new().unwrap();
    fs::create_dir_all(dir.path().join("scripts")).unwrap();
    fs::write(dir.path().join("scripts/handler.rhai"), "let x = 1; x").unwrap();
    let manifest_path = dir.path().join("picloud.toml");

    // v1: a script with an email trigger.
    let v1 = format!(
        "[app]\nslug = \"{slug}\"\nname = \"M5\"\n\n\
         [secrets]\nnames = [\"email-hmac\"]\n\n\
         [[scripts]]\nname = \"handler\"\nfile = \"scripts/handler.rhai\"\n\n\
         [[triggers.email]]\nscript = \"handler\"\ninbound_secret_ref = \"email-hmac\"\n"
    );
    fs::write(&manifest_path, &v1).unwrap();
    common::pic_as(&env)
        .args(["apply", "--file"])
        .arg(&manifest_path)
        .assert()
        .success();

    // v2: drop the script (and, implicitly, its un-representable email
    // trigger). A prune apply must REFUSE rather than cascade-destroy it.
    let v2 = format!("[app]\nslug = \"{slug}\"\nname = \"M5\"\n");
    fs::write(&manifest_path, &v2).unwrap();
    let out = common::pic_as(&env)
        .args(["apply", "--file"])
        .arg(&manifest_path)
        .args(["--prune", "--yes"])
        .output()
        .expect("apply --prune");
    assert!(
        !out.status.success(),
        "prune must refuse to orphan an email trigger"
    );

    // The script and its email trigger both survive the refused apply.
    let scripts = String::from_utf8(
        common::pic_as(&env)
            .args(["scripts", "ls", "--app", &slug])
            .output()
            .unwrap()
            .stdout,
    )
    .unwrap();
    assert!(
        scripts.contains("handler"),
        "script must survive:\n{scripts}"
    );
    let triggers = String::from_utf8(
        common::pic_as(&env)
            .args(["triggers", "ls", "--app", &slug])
            .output()
            .unwrap()
            .stdout,
    )
    .unwrap();
    assert!(
        triggers.lines().any(|l| l.contains("email")),
        "email trigger must survive:\n{triggers}"
    );
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn apply_email_unset_secret_fails() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let slug = common::unique_slug("m5-nosecret");
    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/handler.rhai"), "let x = 1; x").unwrap();
    let manifest = format!(
        "[app]\nslug = \"{slug}\"\nname = \"M5\"\n\n\
         [[scripts]]\nname = \"handler\"\nfile = \"scripts/handler.rhai\"\n\n\
         [[triggers.email]]\nscript = \"handler\"\ninbound_secret_ref = \"never-set\"\n"
    );
    let manifest_path = dir.path().join("picloud.toml");
    fs::write(&manifest_path, &manifest).unwrap();

    // The referenced secret was never set → apply must fail atomically.
    let out = common::pic_as(&env)
        .args(["apply", "--file"])
        .arg(&manifest_path)
        .output()
        .expect("apply");
    assert!(
        !out.status.success(),
        "apply must fail when an email secret is unset"
    );

    // Atomic: neither the script nor the email trigger was created.
    let s = String::from_utf8(
        common::pic_as(&env)
            .args(["scripts", "ls", "--app", &slug])
            .output()
            .unwrap()
            .stdout,
    )
    .unwrap();
    assert!(
        !s.contains("handler"),
        "failed apply must leave nothing behind:\n{s}"
    );
}
