//! E2E tests for stream table guard triggers (EC-25 + EC-26).
//!
//! Validates that direct DML (INSERT/UPDATE/DELETE) and TRUNCATE on
//! stream tables is blocked by the guard triggers installed at creation.
//!
//! Prerequisites: `./tests/build_e2e_image.sh`

mod e2e;

use e2e::E2eDb;

// ═══════════════════════════════════════════════════════════════════════
// EC-26: Direct INSERT on stream table is blocked
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_guard_trigger_blocks_direct_insert() {
    let db = E2eDb::new().await.with_extension().await;
    db.execute("CREATE TABLE guard_src (id INT PRIMARY KEY, val TEXT)")
        .await;
    db.execute("INSERT INTO guard_src VALUES (1, 'a'), (2, 'b')")
        .await;

    let q = "SELECT id, val FROM guard_src";
    db.create_st("guard_ins_st", q, "1m", "DIFFERENTIAL").await;

    // Direct INSERT should be rejected
    let result = db
        .try_execute("INSERT INTO guard_ins_st (__pgt_row_id, id, val) VALUES (999, 99, 'bad')")
        .await;
    assert!(
        result.is_err(),
        "Direct INSERT on stream table should be blocked"
    );
    let err_msg = result.unwrap_err().to_string();
    assert!(
        err_msg.contains("Direct DML on stream table"),
        "Error should mention guard: {err_msg}"
    );
}

// ═══════════════════════════════════════════════════════════════════════
// EC-26: Direct UPDATE on stream table is blocked
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_guard_trigger_blocks_direct_update() {
    let db = E2eDb::new().await.with_extension().await;
    db.execute("CREATE TABLE guard_upd_src (id INT PRIMARY KEY, val TEXT)")
        .await;
    db.execute("INSERT INTO guard_upd_src VALUES (1, 'a')")
        .await;

    let q = "SELECT id, val FROM guard_upd_src";
    db.create_st("guard_upd_st", q, "1m", "DIFFERENTIAL").await;

    // Direct UPDATE should be rejected
    let result = db
        .try_execute("UPDATE guard_upd_st SET val = 'hacked'")
        .await;
    assert!(
        result.is_err(),
        "Direct UPDATE on stream table should be blocked"
    );
    let err_msg = result.unwrap_err().to_string();
    assert!(
        err_msg.contains("Direct DML on stream table"),
        "Error should mention guard: {err_msg}"
    );
}

// ═══════════════════════════════════════════════════════════════════════
// EC-26: Direct DELETE on stream table is blocked
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_guard_trigger_blocks_direct_delete() {
    let db = E2eDb::new().await.with_extension().await;
    db.execute("CREATE TABLE guard_del_src (id INT PRIMARY KEY, val TEXT)")
        .await;
    db.execute("INSERT INTO guard_del_src VALUES (1, 'a')")
        .await;

    let q = "SELECT id, val FROM guard_del_src";
    db.create_st("guard_del_st", q, "1m", "DIFFERENTIAL").await;

    // Direct DELETE should be rejected
    let result = db.try_execute("DELETE FROM guard_del_st").await;
    assert!(
        result.is_err(),
        "Direct DELETE on stream table should be blocked"
    );
    let err_msg = result.unwrap_err().to_string();
    assert!(
        err_msg.contains("Direct DML on stream table"),
        "Error should mention guard: {err_msg}"
    );
}

// ═══════════════════════════════════════════════════════════════════════
// EC-25: TRUNCATE on stream table is blocked
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_guard_trigger_blocks_truncate() {
    let db = E2eDb::new().await.with_extension().await;
    db.execute("CREATE TABLE guard_trunc_src (id INT PRIMARY KEY, val TEXT)")
        .await;
    db.execute("INSERT INTO guard_trunc_src VALUES (1, 'a'), (2, 'b')")
        .await;

    let q = "SELECT id, val FROM guard_trunc_src";
    db.create_st("guard_trunc_st", q, "1m", "DIFFERENTIAL")
        .await;

    // TRUNCATE should be rejected
    let result = db.try_execute("TRUNCATE guard_trunc_st").await;
    assert!(
        result.is_err(),
        "TRUNCATE on stream table should be blocked"
    );
    let err_msg = result.unwrap_err().to_string();
    assert!(
        err_msg.contains("Direct DML on stream table"),
        "Error should mention guard: {err_msg}"
    );
}

// ═══════════════════════════════════════════════════════════════════════
// EC-26: Managed refresh still works (guard bypassed internally)
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_guard_trigger_allows_managed_refresh() {
    let db = E2eDb::new().await.with_extension().await;
    db.execute("CREATE TABLE guard_ok_src (id INT PRIMARY KEY, val INT)")
        .await;
    db.execute("INSERT INTO guard_ok_src VALUES (1, 10), (2, 20)")
        .await;

    let q = "SELECT id, val FROM guard_ok_src";
    db.create_st("guard_ok_st", q, "1m", "DIFFERENTIAL").await;
    db.assert_st_matches_query("guard_ok_st", q).await;

    // Make a change and refresh — should succeed despite guard
    db.execute("INSERT INTO guard_ok_src VALUES (3, 30)").await;
    db.refresh_st("guard_ok_st").await;
    db.assert_st_matches_query("guard_ok_st", q).await;
}
