//! `enabled` three-state lifecycle, end to end: disabling a script via the
//! manifest makes it non-invocable (404 on the execute-by-id bypass), and
//! re-enabling restores it — proving the data path + runtime honoring.

use std::fs;
use std::path::Path;

use serde_json::Value;
use tempfile::TempDir;

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

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn disabling_a_script_makes_it_uninvocable_then_reenable() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let slug = common::unique_slug("enabled");
    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"), "\"hi\"").unwrap();
    let manifest_path = dir.path().join("picloud.toml");
    let manifest = |enabled_line: &str| {
        format!(
            "[app]\nslug = \"{slug}\"\nname = \"Enabled\"\n\n\
             [[scripts]]\nname = \"hello\"\nfile = \"scripts/hello.rhai\"\n{enabled_line}"
        )
    };

    // Active → invocable.
    fs::write(&manifest_path, manifest("")).unwrap();
    apply(&env, &manifest_path);
    let id = script_id(&env, &slug, "hello");
    assert_eq!(invoke_status(&env, &id), 200, "active script must invoke");

    // Disabled → 404 (not invocable), but still deployed (re-pull would show it).
    fs::write(&manifest_path, manifest("enabled = false\n")).unwrap();
    apply(&env, &manifest_path);
    assert_eq!(
        invoke_status(&env, &id),
        404,
        "disabled script must 404 on execute-by-id"
    );

    // Re-enabled → invocable again.
    fs::write(&manifest_path, manifest("enabled = true\n")).unwrap();
    apply(&env, &manifest_path);
    assert_eq!(
        invoke_status(&env, &id),
        200,
        "re-enabled script must invoke"
    );
}

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

    // The app must claim a Host before its routes are reachable (two-phase
    // dispatch: Host → app → route). A unique strict host avoids colliding
    // with other tests' instance-global claims.
    let host = format!("{slug}.test");
    common::pic_as(&env)
        .args(["apps", "domains", "add", &slug, &host])
        .assert()
        .success();

    let dir = TempDir::new().unwrap();
    fs::create_dir_all(dir.path().join("scripts")).unwrap();
    fs::write(dir.path().join("scripts/hello.rhai"), "\"hi\"").unwrap();
    let manifest_path = dir.path().join("picloud.toml");
    let manifest = |enabled_line: &str| {
        format!(
            "[app]\nslug = \"{slug}\"\nname = \"EnabledRoute\"\n\n\
             [[scripts]]\nname = \"hello\"\nfile = \"scripts/hello.rhai\"\n\n\
             [[routes]]\nscript = \"hello\"\nmethod = \"GET\"\n\
             host_kind = \"any\"\npath_kind = \"exact\"\npath = \"/hello\"\n{enabled_line}"
        )
    };

    // Active → the route serves.
    fs::write(&manifest_path, manifest("")).unwrap();
    apply(&env, &manifest_path);
    assert_eq!(
        route_status(&env, &host, "/hello"),
        200,
        "active route serves"
    );

    // Disabled → 404, indistinguishable from absent.
    fs::write(&manifest_path, manifest("enabled = false\n")).unwrap();
    apply(&env, &manifest_path);
    assert_eq!(
        route_status(&env, &host, "/hello"),
        404,
        "disabled route must 404"
    );

    // Re-enabled → serves again.
    fs::write(&manifest_path, manifest("enabled = true\n")).unwrap();
    apply(&env, &manifest_path);
    assert_eq!(
        route_status(&env, &host, "/hello"),
        200,
        "re-enabled route serves"
    );
}

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

    let dir = TempDir::new().unwrap();
    fs::create_dir_all(dir.path().join("scripts")).unwrap();
    fs::write(dir.path().join("scripts/hello.rhai"), "\"hi\"").unwrap();
    let manifest_path = dir.path().join("picloud.toml");
    // Route stays enabled; the SCRIPT it binds is disabled (route-on/script-off).
    fs::write(
        &manifest_path,
        format!(
            "[app]\nslug = \"{slug}\"\nname = \"OS\"\n\n\
             [[scripts]]\nname = \"hello\"\nfile = \"scripts/hello.rhai\"\nenabled = false\n\n\
             [[routes]]\nscript = \"hello\"\nmethod = \"GET\"\n\
             host_kind = \"any\"\npath_kind = \"exact\"\npath = \"/hello\"\n"
        ),
    )
    .unwrap();
    apply(&env, &manifest_path);

    // 404, and the body must be the flat "no route matches" form — never the
    // internal script id (no info leak; indistinguishable from absent).
    let client = reqwest::blocking::Client::new();
    let resp = client
        .get(format!("{}/hello", env.url))
        .header(reqwest::header::HOST, &host)
        .send()
        .unwrap();
    assert_eq!(resp.status().as_u16(), 404);
    let body = resp.text().unwrap();
    assert!(body.contains("no route matches"), "flat 404 body: {body}");
}

fn apply(env: &common::TestEnv, manifest_path: &Path) {
    common::pic_as(env)
        .args(["apply", "--file"])
        .arg(manifest_path)
        .assert()
        .success();
}

/// GET a user route under an explicit `Host` (the claimed domain) and return
/// the HTTP status code.
fn route_status(env: &common::TestEnv, host: &str, path: &str) -> u16 {
    let client = reqwest::blocking::Client::new();
    client
        .get(format!("{}{path}", env.url))
        .header(reqwest::header::HOST, host)
        .send()
        .unwrap()
        .status()
        .as_u16()
}

/// Resolve a script's id via the admin API (the manifest carries no ids).
fn script_id(env: &common::TestEnv, slug: &str, name: &str) -> String {
    let client = reqwest::blocking::Client::new();
    let scripts: Vec<Value> = client
        .get(format!("{}/api/v1/admin/scripts?app={slug}", env.url))
        .bearer_auth(&env.token)
        .send()
        .unwrap()
        .json()
        .unwrap();
    scripts
        .into_iter()
        .find(|s| s["name"] == name)
        .and_then(|s| s["id"].as_str().map(String::from))
        .expect("script id")
}

/// POST the execute-by-id bypass and return the HTTP status code.
fn invoke_status(env: &common::TestEnv, id: &str) -> u16 {
    let client = reqwest::blocking::Client::new();
    client
        .post(format!("{}/api/v1/execute/{id}", env.url))
        .bearer_auth(&env.token)
        .body("{}")
        .send()
        .unwrap()
        .status()
        .as_u16()
}
