//! E2E tests for ROWS FROM with multiple set-returning functions.
//!
//! Validates the `rewrite_rows_from()` pass that transforms
//! `ROWS FROM(f1(), f2(), ...)` into either multi-arg `unnest()` (all-unnest
//! optimisation) or an ordinal-based LEFT JOIN LATERAL chain (general case).
//!
//! Prerequisites: `./tests/build_e2e_image.sh`

mod e2e;

use e2e::E2eDb;

// ═══════════════════════════════════════════════════════════════════════════
//  All-unnest optimisation (multi-arg unnest merge)
// ═══════════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_rows_from_dual_unnest_full() {
    let db = E2eDb::new().await.with_extension().await;

    db.execute("CREATE TABLE rf_src (id INT PRIMARY KEY, names TEXT[], scores INT[])")
        .await;
    db.execute(
        "INSERT INTO rf_src VALUES \
         (1, ARRAY['alice', 'bob', 'carol'], ARRAY[90, 85, 70]), \
         (2, ARRAY['dave'], ARRAY[60, 95])",
    )
    .await;

    // ROWS FROM(unnest(names), unnest(scores)) → rewritten to unnest(names, scores)
    db.create_st(
        "rf_dual_unnest",
        "SELECT rf.id, u.name, u.score \
         FROM rf_src rf, \
         ROWS FROM(unnest(rf.names), unnest(rf.scores)) AS u(name, score)",
        "1m",
        "FULL",
    )
    .await;

    let (status, mode, populated, errors) = db.pgt_status("rf_dual_unnest").await;
    assert_eq!(status, "ACTIVE");
    assert_eq!(mode, "FULL");
    assert!(populated);
    assert_eq!(errors, 0);

    // id=1: 3 rows (alice/90, bob/85, carol/70)
    // id=2: 2 rows (dave/60, NULL/95)
    let query = "SELECT rf.id, u.name, u.score \
         FROM rf_src rf, \
         ROWS FROM(unnest(rf.names), unnest(rf.scores)) AS u(name, score)";
    db.assert_st_matches_query("rf_dual_unnest", query).await;
}

#[tokio::test]
async fn test_rows_from_triple_unnest_full() {
    let db = E2eDb::new().await.with_extension().await;

    db.execute("CREATE TABLE rf_tri (id INT PRIMARY KEY, a TEXT[], b INT[], c BOOLEAN[])")
        .await;
    db.execute(
        "INSERT INTO rf_tri VALUES \
         (1, ARRAY['x', 'y'], ARRAY[10, 20, 30], ARRAY[true])",
    )
    .await;

    db.create_st(
        "rf_tri_unnest",
        "SELECT t.id, u.col_a, u.col_b, u.col_c \
         FROM rf_tri t, \
         ROWS FROM(unnest(t.a), unnest(t.b), unnest(t.c)) AS u(col_a, col_b, col_c)",
        "1m",
        "FULL",
    )
    .await;

    let (status, _, populated, errors) = db.pgt_status("rf_tri_unnest").await;
    assert_eq!(status, "ACTIVE");
    assert!(populated);
    assert_eq!(errors, 0);

    // Longest array has 3 elements, so 3 rows total (shorter arrays padded with NULL)
    let query = "SELECT t.id, u.col_a, u.col_b, u.col_c \
         FROM rf_tri t, \
         ROWS FROM(unnest(t.a), unnest(t.b), unnest(t.c)) AS u(col_a, col_b, col_c)";
    db.assert_st_matches_query("rf_tri_unnest", query).await;
}

// ═══════════════════════════════════════════════════════════════════════════
//  General case (ordinal-based LEFT JOIN LATERAL chain)
// ═══════════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_rows_from_mixed_srfs_full() {
    let db = E2eDb::new().await.with_extension().await;

    db.execute("CREATE TABLE rf_mixed (id INT PRIMARY KEY, arr TEXT[])")
        .await;
    db.execute(
        "INSERT INTO rf_mixed VALUES \
         (1, ARRAY['a', 'b', 'c'])",
    )
    .await;

    // ROWS FROM(unnest(arr), generate_series(1, 5)) — mixed SRF types
    // triggers the general (non-all-unnest) rewrite path.
    db.create_st(
        "rf_mixed_srfs",
        "SELECT m.id, u.val, u.n \
         FROM rf_mixed m, \
         ROWS FROM(unnest(m.arr), generate_series(1, 5)) AS u(val, n)",
        "1m",
        "FULL",
    )
    .await;

    let (status, _, populated, errors) = db.pgt_status("rf_mixed_srfs").await;
    assert_eq!(status, "ACTIVE");
    assert!(populated);
    assert_eq!(errors, 0);

    // unnest produces 3 rows, generate_series produces 5 rows.
    // ROWS FROM zips them → 5 rows (the longer of the two).
    let query = "SELECT m.id, u.val, u.n \
         FROM rf_mixed m, \
         ROWS FROM(unnest(m.arr), generate_series(1, 5)) AS u(val, n)";
    db.assert_st_matches_query("rf_mixed_srfs", query).await;
}

// ═══════════════════════════════════════════════════════════════════════════
//  Differential mode — verifies rewrite works with IVM refresh
// ═══════════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_rows_from_dual_unnest_differential_insert() {
    let db = E2eDb::new().await.with_extension().await;

    db.execute("CREATE TABLE rf_diff (id INT PRIMARY KEY, xs INT[], ys INT[])")
        .await;
    db.execute("INSERT INTO rf_diff VALUES (1, ARRAY[10, 20], ARRAY[100, 200])")
        .await;

    let query = "SELECT d.id, u.x, u.y \
         FROM rf_diff d, \
         ROWS FROM(unnest(d.xs), unnest(d.ys)) AS u(x, y)";

    db.create_st("rf_diff_view", query, "1m", "DIFFERENTIAL")
        .await;

    db.assert_st_matches_query("rf_diff_view", query).await;

    // Insert a new row
    db.execute("INSERT INTO rf_diff VALUES (2, ARRAY[30, 40, 50], ARRAY[300])")
        .await;
    db.refresh_st("rf_diff_view").await;

    // id=1: 2 rows, id=2: 3 rows = 5 total
    db.assert_st_matches_query("rf_diff_view", query).await;
}

#[tokio::test]
async fn test_rows_from_dual_unnest_differential_delete() {
    let db = E2eDb::new().await.with_extension().await;

    db.execute("CREATE TABLE rf_del (id INT PRIMARY KEY, xs INT[], ys TEXT[])")
        .await;
    db.execute(
        "INSERT INTO rf_del VALUES \
         (1, ARRAY[1, 2], ARRAY['a', 'b']), \
         (2, ARRAY[3], ARRAY['c'])",
    )
    .await;

    let query = "SELECT d.id, u.x, u.y \
         FROM rf_del d, \
         ROWS FROM(unnest(d.xs), unnest(d.ys)) AS u(x, y)";

    db.create_st("rf_del_view", query, "1m", "DIFFERENTIAL")
        .await;

    // id=1: 2 rows, id=2: 1 row = 3
    db.assert_st_matches_query("rf_del_view", query).await;

    db.execute("DELETE FROM rf_del WHERE id = 1").await;
    db.refresh_st("rf_del_view").await;

    // Only id=2 remains: 1 row
    db.assert_st_matches_query("rf_del_view", query).await;
}

// ═══════════════════════════════════════════════════════════════════════════
//  Single-function ROWS FROM (no rewrite needed — pass-through)
// ═══════════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_rows_from_single_function_passthrough() {
    let db = E2eDb::new().await.with_extension().await;

    db.execute("CREATE TABLE rf_single (id INT PRIMARY KEY, arr INT[])")
        .await;
    db.execute("INSERT INTO rf_single VALUES (1, ARRAY[10, 20, 30])")
        .await;

    let query = "SELECT s.id, u.val \
         FROM rf_single s, \
         ROWS FROM(unnest(s.arr)) AS u(val)";

    // Single-function ROWS FROM — should pass through without rewriting
    db.create_st("rf_single_view", query, "1m", "FULL").await;

    let (status, _, populated, errors) = db.pgt_status("rf_single_view").await;
    assert_eq!(status, "ACTIVE");
    assert!(populated);
    assert_eq!(errors, 0);
    db.assert_st_matches_query("rf_single_view", query).await;
}
