//! `pic scripts invoke` — body sources (inline, `@file`, `@-`), header
//! propagation, exit-code semantics for non-2xx responses, and 404
//! handling for unknown ids.

use predicates::prelude::*;
use serde_json::Value;

use crate::common;

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn invoke_with_inline_json_body_echoes() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (id, _guard) = common::deploy_fixture(&env, "invoke-inline", "echo.rhai");

    let out = common::pic_as(&env)
        .args(["scripts", "invoke", &id, "--body", r#"{"x":1}"#])
        .output()
        .expect("invoke");
    assert!(out.status.success(), "invoke failed: {out:?}");
    let parsed: Value = serde_json::from_slice(&out.stdout).expect("stdout JSON");
    assert_eq!(parsed["body"]["x"], 1, "echoed body: {parsed}");
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn invoke_with_file_body() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (id, _guard) = common::deploy_fixture(&env, "invoke-file", "echo.rhai");

    let tmp = tempfile::NamedTempFile::new().expect("tempfile");
    std::fs::write(tmp.path(), r#"{"src":"file"}"#).unwrap();
    let body_arg = format!("@{}", tmp.path().display());

    let out = common::pic_as(&env)
        .args(["scripts", "invoke", &id, "--body", &body_arg])
        .output()
        .expect("invoke");
    assert!(out.status.success(), "invoke failed: {out:?}");
    let parsed: Value = serde_json::from_slice(&out.stdout).expect("stdout JSON");
    assert_eq!(parsed["body"]["src"], "file", "echoed body: {parsed}");
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn invoke_with_stdin_body() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (id, _guard) = common::deploy_fixture(&env, "invoke-stdin", "echo.rhai");

    let assert = common::pic_as(&env)
        .args(["scripts", "invoke", &id, "--body", "@-"])
        .write_stdin(r#"{"src":"stdin"}"#)
        .assert()
        .success();
    let out = assert.get_output();
    let parsed: Value = serde_json::from_slice(&out.stdout).expect("stdout JSON");
    assert_eq!(parsed["body"]["src"], "stdin", "echoed body: {parsed}");
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn invoke_propagates_headers() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (id, _guard) = common::deploy_fixture(&env, "invoke-hdr", "echo.rhai");

    let out = common::pic_as(&env)
        .args([
            "scripts",
            "invoke",
            &id,
            "-H",
            "X-Foo: bar",
            "-H",
            "X-Baz=qux",
        ])
        .output()
        .expect("invoke");
    assert!(out.status.success(), "invoke failed: {out:?}");
    let parsed: Value = serde_json::from_slice(&out.stdout).expect("stdout JSON");
    // HTTP normalises header names to lowercase.
    assert_eq!(parsed["headers"]["x-foo"], "bar", "echoed: {parsed}");
    assert_eq!(parsed["headers"]["x-baz"], "qux", "echoed: {parsed}");
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn invoke_unknown_script_id_errors() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);

    // Any well-formed UUID that doesn't exist server-side. The
    // orchestrator's `/execute/{id}` handler returns 404 specifically
    // for unknown ids — tighten the predicate so a regressed 401
    // wouldn't sneak through.
    let bogus = "00000000-0000-0000-0000-000000000000";
    common::pic_as(&env)
        .args(["scripts", "invoke", bogus])
        .assert()
        .failure()
        .stderr(predicate::str::contains("HTTP 404"));
}

/// `pic invoke <id>` (top-level alias) and `pic scripts invoke <id>`
/// must hit the same handler and produce identical-shape stdout.
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn top_level_invoke_alias_works() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (id, _guard) = common::deploy_fixture(&env, "inv-alias", "hello.rhai");

    let nested = common::pic_as(&env)
        .args(["scripts", "invoke", &id])
        .output()
        .expect("scripts invoke");
    assert!(nested.status.success());
    let nested_body: Value = serde_json::from_slice(&nested.stdout).unwrap();

    let aliased = common::pic_as(&env)
        .args(["invoke", &id])
        .output()
        .expect("invoke (top-level)");
    assert!(aliased.status.success());
    let aliased_body: Value = serde_json::from_slice(&aliased.stdout).unwrap();

    assert_eq!(
        nested_body, aliased_body,
        "top-level alias should produce identical body to scripts invoke"
    );
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn invoke_non_2xx_exits_nonzero_but_prints_body() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (id, _guard) = common::deploy_fixture(&env, "invoke-500", "boom.rhai");

    let out = common::pic_as(&env)
        .args(["scripts", "invoke", &id])
        .output()
        .expect("invoke");
    assert!(!out.status.success(), "expected non-zero exit: {out:?}");
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("<- HTTP 500"),
        "stderr should report HTTP 500: {stderr}"
    );
    let parsed: Value = serde_json::from_slice(&out.stdout)
        .unwrap_or_else(|e| panic!("stdout was not JSON ({e}): {:?}", out.stdout));
    assert_eq!(parsed["ok"], false, "boom body: {parsed}");
    assert_eq!(parsed["why"], "intentional", "boom body: {parsed}");
}
