//! `pic logs <script-id>` — emptiness, status labels, `--limit`
//! clamping, error path for unknown ids, and the 120-char truncate
//! applied to the summary column.

use predicates::prelude::*;

use crate::common;

/// Pick out the data rows from `pic logs` TSV output — the header line
/// (`created_at\tsource\tstatus\tsummary`) is now always present, so the old
/// "no non-empty lines means no logs" check needs to skip it.
fn data_rows(stdout: &str) -> Vec<&str> {
    stdout
        .lines()
        .filter(|l| !l.trim().is_empty())
        .filter(|l| !l.starts_with("created_at"))
        .collect()
}

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

    let out = common::pic_as(&env)
        .args(["logs", &id])
        .output()
        .expect("logs");
    assert!(out.status.success(), "logs failed: {out:?}");
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(
        data_rows(&stdout).is_empty(),
        "expected no log rows (header is allowed), got: {stdout}"
    );
}

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

    common::pic_as(&env)
        .args(["scripts", "invoke", &id])
        .assert()
        .success();

    let out = common::pic_as(&env)
        .args(["logs", &id])
        .output()
        .expect("logs");
    assert!(out.status.success(), "logs failed: {out:?}");
    let stdout = String::from_utf8(out.stdout).unwrap();
    let rows = data_rows(&stdout);
    assert_eq!(rows.len(), 1, "expected 1 data row, got: {stdout}");
    let cols: Vec<&str> = rows[0].split('\t').map(str::trim).collect();
    assert_eq!(
        cols.len(),
        4,
        "row should be 4 tab-delimited cells (created_at, source, status, summary): {rows:?}"
    );
    assert_eq!(cols[2], "success");
}

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

    // The invoke is expected to fail — we only care that the execution
    // gets recorded with `Error` status.
    let _ = common::pic_as(&env)
        .args(["scripts", "invoke", &id])
        .output();

    let out = common::pic_as(&env)
        .args(["logs", &id])
        .output()
        .expect("logs");
    assert!(out.status.success(), "logs failed: {out:?}");
    let stdout = String::from_utf8(out.stdout).unwrap();
    let row = data_rows(&stdout)
        .into_iter()
        .next()
        .expect("at least one data row");
    let cols: Vec<&str> = row.split('\t').map(str::trim).collect();
    assert_eq!(cols[2], "error", "expected error status, got row: {row}");
}

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

    for _ in 0..3 {
        common::pic_as(&env)
            .args(["scripts", "invoke", &id])
            .assert()
            .success();
    }

    let out = common::pic_as(&env)
        .args(["logs", &id, "--limit", "1"])
        .output()
        .expect("logs");
    assert!(out.status.success(), "logs failed: {out:?}");
    let stdout = String::from_utf8(out.stdout).unwrap();
    let rows = data_rows(&stdout).len();
    assert_eq!(rows, 1, "expected --limit 1, got rows: {stdout}");
}

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

    let bogus = "00000000-0000-0000-0000-000000000000";
    common::pic_as(&env)
        .args(["logs", bogus])
        .assert()
        .failure()
        // 404 specifically — same `NotFound(ScriptId)` path the get/edit
        // endpoints use.
        .stderr(predicate::str::contains("HTTP 404"));
}

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

    common::pic_as(&env)
        .args(["scripts", "invoke", &id])
        .assert()
        .success();

    let out = common::pic_as(&env)
        .args(["logs", &id])
        .output()
        .expect("logs");
    assert!(out.status.success(), "logs failed: {out:?}");
    let stdout = String::from_utf8(out.stdout).unwrap();
    let row = data_rows(&stdout)
        .into_iter()
        .next()
        .expect("at least one data row");
    let summary = row.split('\t').nth(3).expect("summary column");
    assert!(
        summary.ends_with('…'),
        "summary should be truncated with `…`, got: {summary}"
    );
    let chars = summary.chars().count();
    assert!(
        chars <= 121,
        "summary should be ≤120 chars + the truncation marker, got {chars}: {summary}"
    );
}
