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

-- Setup: Create playground schema and test data for scenario tests
-- This file runs first (00_) to set up shared test infrastructure

-- ---------------------------------------------------------------------------
-- E2E test roles
--
-- We want the default E2E posture to be: run tests as a non-privileged role.
-- This setup file is expected to run as a superuser (postgres in Docker,
-- or the pgrx superuser in local runs) and will create/grant the test role.
-- ---------------------------------------------------------------------------

DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'df_e2e_user') THEN
        CREATE ROLE df_e2e_user LOGIN;
    END IF;
END $$;

-- ---------------------------------------------------------------------------
-- Reusable helper: wait for the background worker to fully reinitialize
-- after DROP/CREATE EXTENSION. Called by tests 25, 28, 29 and at the end
-- of this setup file so all tests start with the BGW fully ready.
--
-- After CREATE EXTENSION, the BGW detects the new extension, applies all
-- duroxide migrations via ApplyAll, then writes a readiness record to
-- duroxide._worker_ready. We poll that table directly because
-- is_worker_ready() is an internal Rust function, not a SQL-callable API.
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public._e2e_wait_for_worker_ready(
    p_timeout_secs INT DEFAULT 30
) RETURNS void
LANGUAGE plpgsql AS $$
DECLARE
    attempts     INT := 0;
    max_attempts INT := p_timeout_secs * 10;  -- poll every 100ms
    table_exists BOOLEAN;
    is_ready     BOOLEAN;
    dx_schema    TEXT := df.duroxide_schema();
BEGIN
    LOOP
        SELECT EXISTS(
            SELECT 1 FROM information_schema.tables
            WHERE table_schema = dx_schema AND table_name = '_worker_ready'
        ) INTO table_exists;

        IF table_exists THEN
            EXECUTE format(
                'SELECT EXISTS(SELECT 1 FROM %I._worker_ready WHERE schema_version >= 1)',
                dx_schema
            ) INTO is_ready;
        ELSE
            is_ready := FALSE;
        END IF;

        EXIT WHEN is_ready OR attempts >= max_attempts;
        PERFORM pg_sleep(0.1);
        attempts := attempts + 1;
    END LOOP;

    IF NOT is_ready THEN
        RAISE EXCEPTION 'Background worker did not become ready after % seconds. Check server logs.', p_timeout_secs;
    END IF;

    RAISE NOTICE 'Background worker ready (all migrations applied)';
END $$;

-- ---------------------------------------------------------------------------
-- Reusable helper: DROP EXTENSION pg_durable with deadlock retry.
--
-- After a durable function completes, the duroxide runtime may still be
-- acknowledging the orchestration item (ack_orchestration_item). If DROP
-- EXTENSION tries to take AccessExclusiveLock on those tables at the same
-- time, PostgreSQL detects a deadlock. This helper retries on deadlock.
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public._e2e_drop_extension_safe()
RETURNS void
LANGUAGE plpgsql AS $$
DECLARE
    attempts INT := 0;
BEGIN
    LOOP
        BEGIN
            EXECUTE 'DROP EXTENSION IF EXISTS pg_durable CASCADE';
            RETURN;
        EXCEPTION WHEN deadlock_detected THEN
            attempts := attempts + 1;
            IF attempts >= 5 THEN
                RAISE;
            END IF;
            PERFORM pg_sleep(1);
        END;
    END LOOP;
END $$;

-- Install extensions needed by tests (requires superuser)
CREATE EXTENSION IF NOT EXISTS dblink;

-- E2E tests create/drop temporary state tables in the public schema.
-- PG15+ revokes CREATE on public from PUBLIC by default.
GRANT USAGE, CREATE ON SCHEMA public TO df_e2e_user;

-- Grant df privileges to the default E2E user. CREATE EXTENSION no longer
-- grants to PUBLIC, so every non-privileged role needs explicit privileges.
-- include_http => true is needed because tests in 04 and 06 use df.http().
SELECT df.grant_usage('df_e2e_user', include_http => true);

-- Create playground schema
CREATE SCHEMA IF NOT EXISTS playground;

-- Ensure playground objects are owned by the non-privileged E2E role
ALTER SCHEMA playground OWNER TO df_e2e_user;

-- Users table
CREATE TABLE IF NOT EXISTS playground.users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT now()
);

ALTER TABLE playground.users OWNER TO df_e2e_user;

-- Orders table
CREATE TABLE IF NOT EXISTS playground.orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES playground.users(id),
    amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(50) DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT now(),
    processed_at TIMESTAMP
);

ALTER TABLE playground.orders OWNER TO df_e2e_user;

-- Task queue for job processing examples
CREATE TABLE IF NOT EXISTS playground.task_queue (
    id SERIAL PRIMARY KEY,
    payload JSONB NOT NULL,
    status VARCHAR(50) DEFAULT 'pending',
    priority INTEGER DEFAULT 0,
    created_at TIMESTAMP DEFAULT now(),
    started_at TIMESTAMP,
    completed_at TIMESTAMP
);

ALTER TABLE playground.task_queue OWNER TO df_e2e_user;

-- Logs table for function output
CREATE TABLE IF NOT EXISTS playground.logs (
    id SERIAL PRIMARY KEY,
    msg TEXT NOT NULL,
    level VARCHAR(20) DEFAULT 'info',
    created_at TIMESTAMP DEFAULT now()
);

ALTER TABLE playground.logs OWNER TO df_e2e_user;

-- Staging table for ETL examples
CREATE TABLE IF NOT EXISTS playground.staging (
    id SERIAL PRIMARY KEY,
    data JSONB,
    source_id INTEGER,
    processed_at TIMESTAMP
);

ALTER TABLE playground.staging OWNER TO df_e2e_user;

-- Target table for ETL examples
CREATE TABLE IF NOT EXISTS playground.target (
    id SERIAL PRIMARY KEY,
    data JSONB,
    source_id INTEGER,
    processed_at TIMESTAMP,
    loaded_at TIMESTAMP DEFAULT now()
);

ALTER TABLE playground.target OWNER TO df_e2e_user;

-- Insert sample users
INSERT INTO playground.users (name, email, active) VALUES
    ('Alice Johnson', 'alice@example.com', true),
    ('Bob Smith', 'bob@example.com', true),
    ('Carol White', 'carol@example.com', true),
    ('David Brown', 'david@example.com', false),
    ('Eve Davis', 'eve@example.com', true)
ON CONFLICT (email) DO NOTHING;

-- Insert sample orders
INSERT INTO playground.orders (user_id, amount, status) VALUES
    (1, 99.99, 'pending'),
    (1, 149.50, 'completed'),
    (2, 75.00, 'pending'),
    (3, 200.00, 'processing'),
    (3, 50.00, 'pending'),
    (5, 125.00, 'completed');

-- Insert sample tasks
INSERT INTO playground.task_queue (payload, status, priority) VALUES
    ('{"type": "email", "to": "alice@example.com"}', 'pending', 1),
    ('{"type": "email", "to": "bob@example.com"}', 'pending', 2),
    ('{"type": "report", "name": "daily_sales"}', 'pending', 0),
    ('{"type": "cleanup", "target": "temp_files"}', 'completed', 0),
    ('{"type": "sync", "source": "external_api"}', 'pending', 3);

-- Insert staging data for ETL
INSERT INTO playground.staging (data, source_id) VALUES
    ('{"product": "Widget A", "qty": 10}', 1001),
    ('{"product": "Widget B", "qty": 25}', 1002),
    ('{"product": "Gadget X", "qty": 5}', 1003);

SELECT 'Playground schema setup complete' AS status;

-- Wait for the background worker to finish applying migrations before any
-- test can call df.start(). This prevents races where df.start() is rejected
-- because the duroxide schema is not yet initialised.
SELECT public._e2e_wait_for_worker_ready(60);

SELECT 'TEST PASSED' AS result;

