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

-- Tests: df.break() inside JOIN and RACE branches propagates correctly to enclosing loop
-- Repro for: Bug: df.break() inside a JOIN branch is silently ignored
SET SESSION AUTHORIZATION df_e2e_user;

-- === Test 1: df.break() inside a JOIN branch exits the loop ===

DROP TABLE IF EXISTS test_break_join_log;
CREATE TABLE test_break_join_log (id SERIAL, iteration INT, ts TIMESTAMP DEFAULT now());

CREATE TEMP TABLE _test_break_join_state AS
SELECT df.start(
    df.loop(
        'INSERT INTO test_break_join_log (iteration) VALUES ((SELECT COALESCE(MAX(iteration), 0) + 1 FROM test_break_join_log))'
        ~> (
            df.break('done') & 'SELECT ''other_branch'''
        )
    ),
    'test-break-in-join'
) AS instance_id;

DO $$
DECLARE
    v_instance_id TEXT;
    v_status TEXT;
    v_cnt INT;
BEGIN
    SELECT instance_id INTO v_instance_id FROM _test_break_join_state;
    RAISE NOTICE 'Test 1 - df.break() in JOIN branch: instance %', v_instance_id;

    SELECT df.await_instance(v_instance_id, 50) INTO v_status;

    SELECT COUNT(*) INTO v_cnt FROM test_break_join_log;

    IF v_status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED [break-in-join]: expected completed, got %', v_status;
    END IF;

    -- Loop should have run exactly 1 iteration before the break exited it
    IF v_cnt != 1 THEN
        RAISE EXCEPTION 'TEST FAILED [break-in-join]: expected 1 iteration, got % (loop did not exit on break)', v_cnt;
    END IF;

    RAISE NOTICE 'PASSED: df.break() in JOIN branch exits the loop after 1 iteration';
END $$;

DROP TABLE _test_break_join_state;
DROP TABLE test_break_join_log;

-- === Test 2: df.break() inside the winning RACE branch exits the loop ===

DROP TABLE IF EXISTS test_break_race_log;
CREATE TABLE test_break_race_log (id SERIAL, iteration INT, ts TIMESTAMP DEFAULT now());

CREATE TEMP TABLE _test_break_race_state AS
SELECT df.start(
    df.loop(
        'INSERT INTO test_break_race_log (iteration) VALUES ((SELECT COALESCE(MAX(iteration), 0) + 1 FROM test_break_race_log))'
        ~> df.race(df.break('race-done'), 'SELECT pg_sleep(10)')
    ),
    'test-break-in-race'
) AS instance_id;

DO $$
DECLARE
    v_instance_id TEXT;
    v_status TEXT;
    v_cnt INT;
BEGIN
    SELECT instance_id INTO v_instance_id FROM _test_break_race_state;
    RAISE NOTICE 'Test 2 - df.break() in RACE branch: instance %', v_instance_id;

    SELECT df.await_instance(v_instance_id, 50) INTO v_status;

    SELECT COUNT(*) INTO v_cnt FROM test_break_race_log;

    IF v_status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED [break-in-race]: expected completed, got %', v_status;
    END IF;

    -- Loop should have run exactly 1 iteration before the break exited it
    IF v_cnt != 1 THEN
        RAISE EXCEPTION 'TEST FAILED [break-in-race]: expected 1 iteration, got % (loop did not exit on break)', v_cnt;
    END IF;

    RAISE NOTICE 'PASSED: df.break() in RACE winning branch exits the loop after 1 iteration';
END $$;

DROP TABLE _test_break_race_state;
DROP TABLE test_break_race_log;

-- === Test 3: df.break() at the top level (outside any loop) fails with a clear error ===
-- Before issue #148 the break sentinel travelled as a normal Ok result, so a top-level
-- df.break() would COMPLETE the instance with a `{"__break__": true, ...}` value. With the
-- typed NodeError::Break, an uncaught break surfaces as a failure instead.

CREATE TEMP TABLE _test_break_toplevel_state AS
SELECT df.start(
    df.break('top-level-oops'),
    'test-break-top-level'
) AS instance_id;

DO $$
DECLARE
    v_instance_id TEXT;
    v_status TEXT;
BEGIN
    SELECT instance_id INTO v_instance_id FROM _test_break_toplevel_state;
    RAISE NOTICE 'Test 3 - top-level df.break(): instance %', v_instance_id;

    SELECT df.await_instance(v_instance_id, 50) INTO v_status;

    -- The key behavioural change: an uncaught break is a failure, not a completed
    -- instance carrying a break sentinel as its result.
    IF v_status != 'failed' THEN
        RAISE EXCEPTION 'TEST FAILED [break-top-level]: expected failed (break outside a loop), got %', v_status;
    END IF;

    RAISE NOTICE 'PASSED: top-level df.break() fails instead of completing with a sentinel';
END $$;

DROP TABLE _test_break_toplevel_state;

-- === Test 4: df.break() nested in an IF inside a JOIN branch inside a loop exits the loop ===
-- Acceptance criterion for issue #148: `df.if(..., df.break('x'), ...)` inside a JOIN inside a
-- loop correctly exits the outer loop. Exercises the deepest propagation path:
-- BREAK -> IF branch -> JOIN branch (subtree boundary, re-raised from the SubtreeEnvelope) ->
-- enclosing loop. Every compound node must unwind the break via `?` for the loop to exit; if
-- any link swallowed it, the loop would spin forever instead of completing after one iteration.

DROP TABLE IF EXISTS test_break_nested_log;
CREATE TABLE test_break_nested_log (id SERIAL, iteration INT, ts TIMESTAMP DEFAULT now());

CREATE TEMP TABLE _test_break_nested_state AS
SELECT df.start(
    df.loop(
        'INSERT INTO test_break_nested_log (iteration) VALUES ((SELECT COALESCE(MAX(iteration), 0) + 1 FROM test_break_nested_log))'
        ~> (
            df.if(
                'SELECT COUNT(*) >= 1 FROM test_break_nested_log',
                df.break('nested-done'),
                'SELECT ''keep-going'''
            ) & 'SELECT ''other_branch'''
        )
    ),
    'test-break-nested-if-in-join'
) AS instance_id;

DO $$
DECLARE
    v_instance_id TEXT;
    v_status TEXT;
    v_cnt INT;
    v_result TEXT;
BEGIN
    SELECT instance_id INTO v_instance_id FROM _test_break_nested_state;
    RAISE NOTICE 'Test 4 - df.break() in IF in JOIN in loop: instance %', v_instance_id;

    SELECT df.await_instance(v_instance_id, 50) INTO v_status;

    SELECT COUNT(*) INTO v_cnt FROM test_break_nested_log;
    SELECT df.result(v_instance_id) INTO v_result;

    IF v_status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED [break-nested]: expected completed, got %', v_status;
    END IF;

    -- Iteration 1: count is 1 (>= 1), the IF takes the break branch. The break unwinds out of
    -- the IF, out of the JOIN, and is caught by the loop, which exits after a single iteration.
    IF v_cnt != 1 THEN
        RAISE EXCEPTION 'TEST FAILED [break-nested]: expected 1 iteration, got % (nested break did not exit the loop)', v_cnt;
    END IF;

    -- The break value must propagate intact to the loop result. df.break('nested-done')
    -- surfaces as the JSON-encoded string "nested-done" (quotes included); assert exact
    -- equality (not a substring match) so a corrupted or wrapped value is caught. IS DISTINCT
    -- FROM also fails safely if the result is NULL.
    IF v_result IS DISTINCT FROM '"nested-done"' THEN
        RAISE EXCEPTION 'TEST FAILED [break-nested]: expected loop result "nested-done", got %', v_result;
    END IF;

    RAISE NOTICE 'PASSED: df.break() nested in IF-in-JOIN exits the enclosing loop with the break value';
END $$;

DROP TABLE _test_break_nested_state;
DROP TABLE test_break_nested_log;

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