-- Copyright (c) Microsoft Corporation.
-- Licensed under the PostgreSQL License.

-- Merged from: 03_variables, 20_vars, 39_named_results_dot, 41_row_set_expansion, 42_result_name_validation
-- Tests: variable substitution (|=> / df.as()), workflow variables (df.setvar/getvar),
--        named result dot-notation, null-safe accessor, row-set expansion, result name validation
-- Note: Test section from 20_vars includes an HTTP sub-test (requires --features http)
SET SESSION AUTHORIZATION df_e2e_user;

-- === Test: 03_variables ===

DROP TABLE IF EXISTS test_vars_log;
CREATE TABLE test_vars_log (id SERIAL, val TEXT, variant TEXT);

CREATE TEMP TABLE _test_state (instance_id TEXT, variant TEXT);

-- Variant A: Using |=> operator
INSERT INTO _test_state SELECT df.start(
    'SELECT 100 as num' |=> 'x'
    ~> 'INSERT INTO test_vars_log (val, variant) VALUES ($x::text, ''op'')',
    'test-variables-op'
), 'operator';

-- Variant B: Using df.as() function
INSERT INTO _test_state SELECT df.start(
    df.seq(
        df.as('SELECT 200 as num', 'y'),
        'INSERT INTO test_vars_log (val, variant) VALUES ($y::text, ''fn'')'
    ),
    'test-variables-fn'
), 'function';

DO $$
DECLARE
    rec RECORD;
    status TEXT;
    val_result TEXT;
    expected TEXT;
BEGIN
    FOR rec IN SELECT instance_id, variant FROM _test_state LOOP
        RAISE NOTICE 'Testing % variant: %', rec.variant, rec.instance_id;

        SELECT df.await_instance(rec.instance_id) INTO status;

        IF status != 'completed' THEN
            RAISE EXCEPTION 'TEST FAILED [%]: status = %', rec.variant, status;
        END IF;
        
        IF rec.variant = 'operator' THEN
            expected := '100';
            SELECT val INTO val_result FROM test_vars_log WHERE variant = 'op' ORDER BY id DESC LIMIT 1;
        ELSE
            expected := '200';
            SELECT val INTO val_result FROM test_vars_log WHERE variant = 'fn' ORDER BY id DESC LIMIT 1;
        END IF;
        
        IF val_result IS NULL OR val_result NOT LIKE '%' || expected || '%' THEN
            RAISE EXCEPTION 'TEST FAILED [%]: expected value containing %, got %', rec.variant, expected, val_result;
        END IF;
        
        RAISE NOTICE 'PASSED: variables [%]', rec.variant;
    END LOOP;
    
    RAISE NOTICE 'TEST PASSED: variables (both variants)';
END $$;

DROP TABLE _test_state;
DROP TABLE test_vars_log;

-- === Test: 20_vars ===

-- Test 1: Simple variable substitution
SELECT df.clearvars();
SELECT df.setvar('greeting', 'Hello');
SELECT df.setvar('target', 'World');

CREATE TEMP TABLE _test_vars_simple (instance_id TEXT);

INSERT INTO _test_vars_simple SELECT df.start(
    'SELECT ''{greeting}, {target}!'' as message' |=> 'msg'
    ~> 'INSERT INTO playground.logs (msg) VALUES ($msg)',
    'test-vars-simple'
);

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
    log_msg TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_vars_simple;
    RAISE NOTICE 'Testing simple vars: %', inst_id;

    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED: simple vars status = %', status;
    END IF;
    
    SELECT msg INTO log_msg FROM playground.logs ORDER BY id DESC LIMIT 1;
    IF log_msg NOT LIKE '%Hello%World%' THEN
        RAISE EXCEPTION 'TEST FAILED: expected Hello World, got %', log_msg;
    END IF;
    
    RAISE NOTICE 'TEST PASSED: vars_simple';
END $$;

DROP TABLE _test_vars_simple;

-- Test 2: System variables
SELECT df.clearvars();

CREATE TEMP TABLE _test_sys_vars (instance_id TEXT);

INSERT INTO _test_sys_vars SELECT df.start(
    'INSERT INTO playground.logs (msg) VALUES (''Instance: {sys_instance_id}, Label: {sys_label}'')
     RETURNING msg' |=> 'log_result',
    'test-sys-vars'
);

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
    log_msg TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_sys_vars;
    RAISE NOTICE 'Testing system vars: %', inst_id;

    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED: sys vars status = %', status;
    END IF;
    
    SELECT msg INTO log_msg FROM playground.logs WHERE msg LIKE 'Instance:%' ORDER BY id DESC LIMIT 1;
    IF log_msg NOT LIKE '%' || inst_id || '%' THEN
        RAISE EXCEPTION 'TEST FAILED: expected instance_id in log, got %', log_msg;
    END IF;
    IF log_msg NOT LIKE '%test-sys-vars%' THEN
        RAISE EXCEPTION 'TEST FAILED: expected label in log, got %', log_msg;
    END IF;
    
    RAISE NOTICE 'TEST PASSED: sys_vars';
END $$;

DROP TABLE _test_sys_vars;

-- Test 3: Vars in HTTP requests (requires --features http)
SELECT df.clearvars();
SELECT df.setvar('api_base', 'https://httpbingo.org');

CREATE TEMP TABLE _test_vars_http (instance_id TEXT);

INSERT INTO _test_vars_http SELECT df.start(
    (df.http('{api_base}/get', 'GET') |=> 'response')
    ~> 'SELECT ($response::jsonb->>''ok'')::boolean as success',
    'test-vars-http'
);

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_vars_http;
    RAISE NOTICE 'Testing vars in HTTP: %', inst_id;

    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED: vars HTTP status = %', status;
    END IF;
    
    RAISE NOTICE 'TEST PASSED: vars_http';
END $$;

DROP TABLE _test_vars_http;

-- Test 4: Multiple vars combined
SELECT df.clearvars();
SELECT df.setvar('table_name', 'users');
SELECT df.setvar('limit_val', '5');

CREATE TEMP TABLE _test_vars_multi (instance_id TEXT);

INSERT INTO _test_vars_multi SELECT df.start(
    'SELECT name FROM playground.{table_name} LIMIT {limit_val}::int' |=> 'names'
    ~> 'INSERT INTO playground.logs (msg) VALUES (''Fetched from {table_name}'')',
    'test-vars-multi'
);

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_vars_multi;
    RAISE NOTICE 'Testing multiple vars: %', inst_id;

    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED: multi vars status = %', status;
    END IF;
    
    RAISE NOTICE 'TEST PASSED: vars_multi';
END $$;

DROP TABLE _test_vars_multi;

-- Test 5: setvar fails inside workflow
SELECT df.clearvars();

CREATE TEMP TABLE _test_setvar_blocked (instance_id TEXT);

INSERT INTO _test_setvar_blocked SELECT df.start(
    'SELECT df.setvar(''illegal_var'', ''should_fail'')',
    'test-setvar-blocked'
);

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
    node_error TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_setvar_blocked;
    RAISE NOTICE 'Testing setvar blocked in workflow: %', inst_id;

    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'failed' THEN
        RAISE EXCEPTION 'TEST FAILED: expected workflow to fail but status = %', status;
    END IF;
    
    SELECT n.result::text INTO node_error 
    FROM df.nodes n
    WHERE n.instance_id = inst_id AND n.status = 'failed'
    LIMIT 1;
    
    IF node_error NOT LIKE '%cannot be called inside a workflow%' THEN
        RAISE EXCEPTION 'TEST FAILED: expected "cannot be called inside a workflow" error, got: %', node_error;
    END IF;
    
    RAISE NOTICE 'TEST PASSED: setvar_blocked_in_workflow';
END $$;

DROP TABLE _test_setvar_blocked;

SELECT df.clearvars();

-- === Test: 39_named_results_dot ===

-- Test 1: Dot-notation — access specific columns
DROP TABLE IF EXISTS test_dot_results;
CREATE TABLE test_dot_results (id SERIAL, got_id INT, got_content TEXT);

CREATE TEMP TABLE _test_state (instance_id TEXT, variant TEXT);

INSERT INTO _test_state SELECT df.start(
    $$SELECT 42 AS id, 'hello' AS content$$ |=> 'doc'
    ~> $$INSERT INTO test_dot_results (got_id, got_content) VALUES ($doc.id, $doc.content)$$,
    'test-dot-notation'
), 'dot';

DO $$
DECLARE
    rec RECORD;
    status TEXT;
    r_id INT;
    r_content TEXT;
BEGIN
    SELECT instance_id INTO rec FROM _test_state WHERE variant = 'dot';

    SELECT df.await_instance(rec.instance_id) INTO status;

    IF status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED [dot-notation]: status = %', status;
    END IF;

    SELECT got_id, got_content INTO r_id, r_content FROM test_dot_results ORDER BY id DESC LIMIT 1;

    IF r_id != 42 THEN
        RAISE EXCEPTION 'TEST FAILED [dot-notation]: expected id=42, got %', r_id;
    END IF;

    IF r_content != 'hello' THEN
        RAISE EXCEPTION 'TEST FAILED [dot-notation]: expected content=hello, got %', r_content;
    END IF;

    RAISE NOTICE 'PASSED: dot-notation';
END $$;

DROP TABLE _test_state;
DROP TABLE test_dot_results;

-- Test 2: Null-safe accessor — $name.col? substitutes NULL
DROP TABLE IF EXISTS test_nullsafe_results;
CREATE TABLE test_nullsafe_results (id SERIAL, val TEXT);

CREATE TEMP TABLE _test_state2 (instance_id TEXT);

INSERT INTO _test_state2 SELECT df.start(
    $$SELECT NULL::text AS val$$ |=> 'x'
    ~> $$INSERT INTO test_nullsafe_results (val) VALUES (COALESCE($x.val?, 'fallback'))$$,
    'test-null-safe'
);

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
    val_result TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_state2;

    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED [null-safe]: status = %', status;
    END IF;

    SELECT val INTO val_result FROM test_nullsafe_results ORDER BY id DESC LIMIT 1;

    IF val_result != 'fallback' THEN
        RAISE EXCEPTION 'TEST FAILED [null-safe]: expected fallback, got %', val_result;
    END IF;

    RAISE NOTICE 'PASSED: null-safe accessor';
END $$;

DROP TABLE _test_state2;
DROP TABLE test_nullsafe_results;

-- Test 3: Strict fail — $name on empty result fails the instance
CREATE TEMP TABLE _test_state3 (instance_id TEXT);

INSERT INTO _test_state3 SELECT df.start(
    $$SELECT 1 WHERE false$$ |=> 'empty'
    ~> $$SELECT $empty$$,
    'test-strict-fail'
);

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_state3;

    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'failed' THEN
        RAISE EXCEPTION 'TEST FAILED [strict-fail]: expected failed, got %', status;
    END IF;

    RAISE NOTICE 'PASSED: strict fail on no-rows';
END $$;

DROP TABLE _test_state3;

-- === Test: 41_row_set_expansion ===

-- Test 1: $batch.* in FROM clause — multi-row expansion
DROP TABLE IF EXISTS test_rowset_results;
CREATE TABLE test_rowset_results (id SERIAL, total_rows INT);

CREATE TEMP TABLE _test_state (instance_id TEXT, variant TEXT);

INSERT INTO _test_state SELECT df.start(
    $$SELECT id, val FROM (VALUES (1, 'a'), (2, 'b'), (3, 'c')) AS t(id, val)$$ |=> 'batch'
    ~> $$INSERT INTO test_rowset_results (total_rows) SELECT count(*) FROM $batch.*$$,
    'test-rowset-from'
), 'from_clause';

-- Test 2: $batch.* in WHERE IN subquery
DROP TABLE IF EXISTS test_rowset_source;
CREATE TABLE test_rowset_source (id INT, name TEXT);
INSERT INTO test_rowset_source VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Carol'), (4, 'Dave');

DROP TABLE IF EXISTS test_rowset_filtered;
CREATE TABLE test_rowset_filtered (id SERIAL, cnt INT);

INSERT INTO _test_state SELECT df.start(
    $$SELECT id FROM test_rowset_source WHERE id <= 2$$ |=> 'ids'
    ~> $$INSERT INTO test_rowset_filtered (cnt) SELECT count(*) FROM test_rowset_source WHERE id IN (SELECT id FROM $ids.*)$$,
    'test-rowset-where-in'
), 'where_in';

-- Test 3: Empty result set expansion — should not error
DROP TABLE IF EXISTS test_rowset_empty;
CREATE TABLE test_rowset_empty (id SERIAL, total_rows INT);

INSERT INTO _test_state SELECT df.start(
    $$SELECT id FROM test_rowset_source WHERE false$$ |=> 'none'
    ~> $$INSERT INTO test_rowset_empty (total_rows) SELECT count(*) FROM $none.*$$,
    'test-rowset-empty'
), 'empty';

DO $$
DECLARE
    rec RECORD;
    status TEXT;
    int_val INT;
BEGIN
    FOR rec IN SELECT instance_id, variant FROM _test_state ORDER BY variant LOOP
        RAISE NOTICE 'Testing % variant: %', rec.variant, rec.instance_id;

        SELECT df.await_instance(rec.instance_id) INTO status;

        IF status != 'completed' THEN
            RAISE EXCEPTION 'TEST FAILED [%]: status = %', rec.variant, status;
        END IF;

        IF rec.variant = 'empty' THEN
            SELECT total_rows INTO int_val FROM test_rowset_empty ORDER BY id DESC LIMIT 1;
            IF int_val != 0 THEN
                RAISE EXCEPTION 'TEST FAILED [empty]: expected 0 rows, got %', int_val;
            END IF;

        ELSIF rec.variant = 'from_clause' THEN
            SELECT total_rows INTO int_val FROM test_rowset_results ORDER BY id ASC LIMIT 1;
            IF int_val != 3 THEN
                RAISE EXCEPTION 'TEST FAILED [from_clause]: expected 3 rows, got %', int_val;
            END IF;

        ELSIF rec.variant = 'where_in' THEN
            SELECT cnt INTO int_val FROM test_rowset_filtered ORDER BY id DESC LIMIT 1;
            IF int_val != 2 THEN
                RAISE EXCEPTION 'TEST FAILED [where_in]: expected 2 rows, got %', int_val;
            END IF;
        END IF;

        RAISE NOTICE 'PASSED: row-set expansion [%]', rec.variant;
    END LOOP;

    RAISE NOTICE 'TEST PASSED: row-set expansion (all variants)';
END $$;

DROP TABLE _test_state;
DROP TABLE test_rowset_results;
DROP TABLE test_rowset_empty;
DROP TABLE test_rowset_filtered;
DROP TABLE test_rowset_source;

-- === Test: 42_result_name_validation ===

-- Test 1: Valid names should work
DROP TABLE IF EXISTS test_name_valid_results;
CREATE TABLE test_name_valid_results (id SERIAL, val INT);

CREATE TEMP TABLE _test_state (instance_id TEXT, variant TEXT);

INSERT INTO _test_state SELECT df.start(
    $$SELECT 42 AS num$$ |=> 'my_result'
    ~> $$INSERT INTO test_name_valid_results (val) VALUES ($my_result)$$,
    'test-name-valid'
), 'valid_name';

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_state WHERE variant = 'valid_name';
    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED [valid_name]: status = %', status;
    END IF;

    RAISE NOTICE 'PASSED: valid result name accepted';
END $$;

-- Test 2: SQL injection attempt should be rejected at DSL time
DO $$
BEGIN
    PERFORM df.start(
        df.as(df.sql('SELECT 1'), 'x) UNION SELECT version()--')
        ~> df.sql('SELECT 1'),
        'test-injection'
    );
    RAISE EXCEPTION 'TEST FAILED: injection name was not rejected';
EXCEPTION
    WHEN OTHERS THEN
        IF SQLERRM LIKE '%not a valid identifier%' THEN
            RAISE NOTICE 'PASSED: injection name correctly rejected: %', SQLERRM;
        ELSE
            RAISE EXCEPTION 'TEST FAILED: unexpected error: %', SQLERRM;
        END IF;
END $$;

-- Test 3: Name with spaces should be rejected
DO $$
BEGIN
    PERFORM df.sql('SELECT 1') |=> 'has space';
    RAISE EXCEPTION 'TEST FAILED: spaced name was not rejected';
EXCEPTION
    WHEN OTHERS THEN
        IF SQLERRM LIKE '%not a valid identifier%' THEN
            RAISE NOTICE 'PASSED: spaced name correctly rejected: %', SQLERRM;
        ELSE
            RAISE EXCEPTION 'TEST FAILED: unexpected error: %', SQLERRM;
        END IF;
END $$;

-- Test 4: Name starting with digit should be rejected
DO $$
BEGIN
    PERFORM df.sql('SELECT 1') |=> '123abc';
    RAISE EXCEPTION 'TEST FAILED: digit-start name was not rejected';
EXCEPTION
    WHEN OTHERS THEN
        IF SQLERRM LIKE '%not a valid identifier%' THEN
            RAISE NOTICE 'PASSED: digit-start name correctly rejected: %', SQLERRM;
        ELSE
            RAISE EXCEPTION 'TEST FAILED: unexpected error: %', SQLERRM;
        END IF;
END $$;

DROP TABLE _test_state;
DROP TABLE test_name_valid_results;

RESET SESSION AUTHORIZATION;
SELECT 'TEST PASSED' AS result;
