//! `pic triggers` smoke tests. Covers the create-cron / create-kv /
//! create-dead-letter happy paths plus a generic create-from-json
//! escape hatch.

use predicates::prelude::*;

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

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn create_kv_and_show_in_ls() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (script_id, guard) = common::deploy_fixture(&env, "trg-kv", "hello.rhai");
    let app = guard.slug().to_string();

    let out = common::pic_as(&env)
        .args([
            "triggers",
            "create-kv",
            "--app",
            &app,
            "--script",
            &script_id,
            "--collection",
            "users",
            "--op",
            "insert",
            "--op",
            "update",
        ])
        .output()
        .expect("create-kv");
    assert!(
        out.status.success(),
        "create-kv failed: {out:?}\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(stdout.contains("kind") && stdout.contains("kv"));

    let out = common::pic_as(&env)
        .args(["triggers", "ls", "--app", &app])
        .output()
        .expect("triggers ls");
    let stdout = String::from_utf8(out.stdout).unwrap();
    let header = stdout.lines().next().expect("header");
    assert_eq!(
        common::cells(header),
        vec![
            "id",
            "kind",
            "script_id",
            "enabled",
            "dispatch",
            "retry_max",
            "materialized",
            "created_at"
        ]
    );
    let row = stdout
        .lines()
        .skip(1)
        .map(common::cells)
        .find(|c| c.get(1).copied() == Some("kv"))
        .unwrap_or_else(|| panic!("kv trigger missing from ls: {stdout}"));
    assert_eq!(row[2], script_id);
    // D1: a hand-authored trigger is not materialized.
    assert_eq!(
        row[6], "false",
        "an app-authored trigger shows materialized = false"
    );
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn create_cron_persists_schedule() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (script_id, guard) = common::deploy_fixture(&env, "trg-cron", "hello.rhai");
    let app = guard.slug().to_string();

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

    let out = common::pic_as(&env)
        .args(["triggers", "ls", "--app", &app])
        .output()
        .expect("triggers ls");
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(
        stdout.lines().skip(1).any(|l| l.contains("cron")),
        "cron trigger missing: {stdout}"
    );
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn create_dead_letter_with_filter() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (script_id, guard) = common::deploy_fixture(&env, "trg-dl", "hello.rhai");
    let app = guard.slug().to_string();

    common::pic_as(&env)
        .args([
            "triggers",
            "create-dead-letter",
            "--app",
            &app,
            "--script",
            &script_id,
            "--source-filter",
            "queue",
        ])
        .assert()
        .success();
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn create_from_json_inline_body_succeeds() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (script_id, guard) = common::deploy_fixture(&env, "trg-json", "hello.rhai");
    let app = guard.slug().to_string();

    let body =
        format!(r#"{{"script_id":"{script_id}","collection_glob":"*","dispatch_mode":"async"}}"#);
    common::pic_as(&env)
        .args([
            "triggers",
            "create-from-json",
            "--app",
            &app,
            "--kind",
            "kv",
            "--body",
            &body,
        ])
        .assert()
        .success();
}

#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn rm_drops_trigger_from_ls() {
    let Some(fx) = common::fixture_or_skip() else {
        return;
    };
    let env = common::admin_env(fx);
    let (script_id, guard) = common::deploy_fixture(&env, "trg-rm", "hello.rhai");
    let app = guard.slug().to_string();

    // Create + capture id from ls
    common::pic_as(&env)
        .args([
            "triggers",
            "create-cron",
            "--app",
            &app,
            "--script",
            &script_id,
            "--schedule",
            "0 0 * * * *",
        ])
        .assert()
        .success();

    let out = common::pic_as(&env)
        .args(["triggers", "ls", "--app", &app])
        .output()
        .expect("triggers ls");
    let stdout = String::from_utf8(out.stdout).unwrap();
    let row = stdout
        .lines()
        .skip(1)
        .map(common::cells)
        .find(|c| c.get(1).copied() == Some("cron"))
        .unwrap_or_else(|| panic!("cron trigger missing: {stdout}"));
    let id = row[0].to_string();

    common::pic_as(&env)
        .args(["triggers", "rm", "--app", &app, &id])
        .assert()
        .success()
        .stdout(predicate::str::contains(format!("Deleted trigger {id}")));

    let out = common::pic_as(&env)
        .args(["triggers", "ls", "--app", &app])
        .output()
        .expect("triggers ls after rm");
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(
        !stdout.lines().any(|l| l.contains(&id)),
        "deleted trigger still in ls: {stdout}"
    );

    // Belt-and-braces so guard ordering is explicit.
    drop(guard);
}

/// Belt-and-braces: explicit binding so AppGuard isn't unused-warned.
#[allow(dead_code)]
fn _force_guard_in_scope() {
    let _: Option<AppGuard> = None;
}
