//! `pic pull` journey: stand up an app with a script, route, cron trigger,
//! and a secret, then export it and assert the manifest + script file.
//!
//! B3: a second case exports a GROUP node (`pic pull --group`) — its scripts,
//! a `sealed`+`shared` `[[triggers.kv]]` template, a `[[routes]]` template, a
//! shared `collections` entry, and a `[vars]` value — and asserts the exported
//! manifest re-applies clean (the round-trip carries the group-only flags).

use std::fs;

use tempfile::TempDir;

use crate::common;
use crate::common::cleanup::{GroupGuard, ScriptGuard};

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

    // App + script "hello" (deploy derives the name from the file stem).
    let (script_id, guard) = common::deploy_fixture(&env, "pull", "hello.rhai");
    let app = guard.slug().to_string();

    // Route → script.
    common::pic_as(&env)
        .args([
            "routes", "create", "--script", &script_id, "--path", "/hook", "--method", "POST",
        ])
        .assert()
        .success();

    // Cron trigger → script.
    common::pic_as(&env)
        .args([
            "triggers",
            "create-cron",
            "--app",
            &app,
            "--script",
            &script_id,
            "--schedule",
            "0 0 * * * *",
        ])
        .assert()
        .success();

    // Secret (name only ends up in the manifest; value stays server-side).
    common::pic_as(&env)
        .args(["secrets", "set", "--app", &app, "api_key"])
        .write_stdin("xyzzy")
        .assert()
        .success();

    // Pull into a scratch dir.
    let out_dir = TempDir::new().expect("pull tempdir");
    common::pic_as(&env)
        .args(["pull", &app, "--dir"])
        .arg(out_dir.path())
        .assert()
        .success();

    // Manifest exists and captures every resource.
    let manifest = std::fs::read_to_string(out_dir.path().join("picloud.toml"))
        .expect("picloud.toml should be written");
    assert!(
        manifest.contains(&format!("slug = \"{app}\"")),
        "manifest missing app slug:\n{manifest}"
    );
    assert!(
        manifest.contains("name = \"hello\"") && manifest.contains("scripts/hello.rhai"),
        "manifest missing script entry:\n{manifest}"
    );
    assert!(
        manifest.contains("[[routes]]") && manifest.contains("path = \"/hook\""),
        "manifest missing route:\n{manifest}"
    );
    assert!(
        manifest.contains("[[triggers.cron]]") && manifest.contains("schedule = \"0 0 * * * *\""),
        "manifest missing cron trigger:\n{manifest}"
    );
    assert!(
        manifest.contains("api_key"),
        "manifest missing secret name:\n{manifest}"
    );

    // Script source was written out faithfully.
    let src = std::fs::read_to_string(out_dir.path().join("scripts/hello.rhai"))
        .expect("scripts/hello.rhai should be written");
    assert!(
        src.contains("hello from pic"),
        "exported source mismatch:\n{src}"
    );

    drop(guard);
}

fn group_script_id(env: &common::TestEnv, group: &str, name: &str) -> String {
    let ls = common::pic_as(env)
        .args(["scripts", "ls", "--group", group])
        .output()
        .expect("scripts ls");
    let table = String::from_utf8(ls.stdout).unwrap();
    table
        .lines()
        .map(common::cells)
        .find(|c| c.get(2) == Some(&name))
        .and_then(|c| c.first().map(|s| (*s).to_string()))
        .unwrap_or_else(|| panic!("group script `{name}` not found:\n{table}"))
}

/// B3: `pic pull --group` round-trips a group node, including the group-only
/// `sealed`/`shared` template flags and a shared `collections` entry.
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn pull_group_round_trips_templates_and_collections() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let group = common::unique_slug("pullg");

    let _g = GroupGuard::new(&env.url, &env.token, &group);
    common::pic_as(&env)
        .args(["groups", "create", &group])
        .assert()
        .success();

    // A group-owned handler the templates bind.
    let dir = TempDir::new().expect("group pull tempdir");
    fs::create_dir_all(dir.path().join("scripts")).unwrap();
    fs::write(
        dir.path().join("scripts/gh.rhai"),
        r#"log::info("group handler"); "ok""#,
    )
    .unwrap();
    common::pic_as(&env)
        .args(["scripts", "deploy"])
        .arg(dir.path().join("scripts/gh.rhai"))
        .args(["--group", &group, "--name", "gh"])
        .assert()
        .success();
    let _gs = ScriptGuard::new(&env.url, &env.token, &group_script_id(&env, &group, "gh"));

    // The group declares a shared collection, a sealed+shared kv trigger
    // template, a sealed route template, and a var.
    let gmanifest = format!(
        "[group]\nslug = \"{group}\"\nname = \"PullG\"\n\n\
         collections = [\"catalog\"]\n\n\
         [vars]\ntier = \"gold\"\n\n\
         [[routes]]\nscript = \"gh\"\npath = \"/gpull\"\npath_kind = \"exact\"\n\
         host_kind = \"any\"\nsealed = true\n\n\
         [[triggers.kv]]\nscript = \"gh\"\ncollection_glob = \"catalog\"\nops = [\"insert\"]\n\
         sealed = true\nshared = true\n"
    );
    let gpath = dir.path().join("picloud.toml");
    fs::write(&gpath, &gmanifest).unwrap();
    common::pic_as(&env)
        .args(["apply", "--file"])
        .arg(&gpath)
        .assert()
        .success();

    // Pull the group into a FRESH dir.
    let out_dir = TempDir::new().expect("pull out dir");
    common::pic_as(&env)
        .args(["pull", "--group", &group, "--dir"])
        .arg(out_dir.path())
        .assert()
        .success();

    let manifest =
        std::fs::read_to_string(out_dir.path().join("picloud.toml")).expect("picloud.toml written");
    assert!(
        manifest.contains("[group]") && manifest.contains(&format!("slug = \"{group}\"")),
        "manifest must be a [group] node:\n{manifest}"
    );
    assert!(
        manifest.contains("catalog"),
        "shared collection must round-trip:\n{manifest}"
    );
    assert!(
        manifest.contains("tier = \"gold\"") || manifest.contains("tier = 'gold'"),
        "group var must round-trip:\n{manifest}"
    );
    assert!(
        manifest.contains("[[routes]]") && manifest.contains("/gpull"),
        "route template must round-trip:\n{manifest}"
    );
    assert!(
        manifest.contains("[[triggers.kv]]") && manifest.contains("collection_glob = \"catalog\""),
        "kv trigger template must round-trip:\n{manifest}"
    );
    // The group-only flags survive the round-trip (the whole point of B3).
    assert!(
        manifest.matches("sealed = true").count() >= 2,
        "both the route and trigger `sealed = true` must round-trip:\n{manifest}"
    );
    assert!(
        manifest.contains("shared = true"),
        "the trigger `shared = true` must round-trip:\n{manifest}"
    );

    // The exported script source landed too.
    let src = std::fs::read_to_string(out_dir.path().join("scripts/gh.rhai"))
        .expect("scripts/gh.rhai written");
    assert!(src.contains("group handler"), "source mismatch:\n{src}");

    // The strong round-trip proof: re-applying the PULLED manifest is accepted
    // (a faithful export re-plans clean; the server would reject a malformed one).
    common::pic_as(&env)
        .args(["apply", "--file"])
        .arg(out_dir.path().join("picloud.toml"))
        .assert()
        .success();
}
