#!/usr/bin/env bash
#
# Run the SIM S0 stock-rover smoke case inside a loopback-only network
# namespace. The script writes factual runtime artifacts to D and never grants
# actuator authority.

set -Eeuo pipefail

readonly EXPECTED_DISTRO="MissionCore-Sim"
readonly D_ROOT="${MISSIONCORE_SIM_D_ROOT:-/mnt/d/NDC_MISSIONCORE/simulation}"
readonly PX4_ROOT="${MISSIONCORE_SIM_PX4_ROOT:-${D_ROOT}/source/PX4-Autopilot}"
readonly AGENT_PREFIX="${MISSIONCORE_SIM_AGENT_PREFIX:-${D_ROOT}/runtime/micro-xrce-dds-agent/2.4.3}"
readonly PX4_MSGS_SETUP="${MISSIONCORE_SIM_PX4_MSGS_SETUP:-${D_ROOT}/build/ros2/px4_msgs/install/setup.bash}"
readonly ARTIFACT_ROOT="${MISSIONCORE_SIM_ARTIFACT_ROOT:-${D_ROOT}/artifacts/s0}"
readonly PROFILE_PATH="${MISSIONCORE_SIM_PROFILE_PATH:-${D_ROOT}/source/qualification-profile.yaml}"
readonly TIME_PROBE="${MISSIONCORE_SIM_TIME_PROBE:-${D_ROOT}/source/gazebo_time_control.py}"
readonly RUN_ID="${1:-$(date -u +%Y%m%dT%H%M%SZ)}"
readonly SPEED_FACTOR="${2:-1}"
readonly RUN_DIR="${ARTIFACT_ROOT}/${RUN_ID}"

if [[ "${SPEED_FACTOR}" != "1" && "${SPEED_FACTOR}" != "2" ]]; then
    echo "error: speed factor must be one of the reviewed values: 1 or 2" >&2
    exit 2
fi

if [[ "${MISSIONCORE_S0_NETNS:-0}" != "1" ]]; then
    if [[ "${EUID}" -ne 0 ]]; then
        echo "error: initial worker-smoke invocation must run as root to create the network namespace" >&2
        exit 2
    fi
    if [[ "${WSL_DISTRO_NAME:-}" != "${EXPECTED_DISTRO}" ]]; then
        echo "error: expected WSL distro ${EXPECTED_DISTRO}, observed ${WSL_DISTRO_NAME:-unset}" >&2
        exit 2
    fi

    exec unshare --net -- bash -c '
        set -e
        ip link set lo up
        exec runuser -u missioncore -- env MISSIONCORE_S0_NETNS=1 "$@"
    ' bash "$0" "$@"
fi

if [[ "${WSL_DISTRO_NAME:-}" != "${EXPECTED_DISTRO}" ]]; then
    echo "error: smoke case escaped the reviewed WSL distribution" >&2
    exit 2
fi

for required_path in "${PX4_ROOT}" "${AGENT_PREFIX}" "${PX4_MSGS_SETUP}" "${ARTIFACT_ROOT}"; do
    if [[ "${required_path}" != /mnt/d/NDC_MISSIONCORE/* ]]; then
        echo "error: mutable or executable path escaped D-only root: ${required_path}" >&2
        exit 2
    fi
done

readonly AGENT_BIN="${AGENT_PREFIX}/bin/MicroXRCEAgent"
readonly AGENT_LOG="${RUN_DIR}/micro-xrce-dds-agent.log"
readonly PX4_LOG="${RUN_DIR}/px4-rover.log"
readonly BRIDGE_LOG="${RUN_DIR}/ros-gz-clock-bridge.log"
readonly TOPICS_LOG="${RUN_DIR}/ros-topics.txt"
readonly VEHICLE_STATUS_LOG="${RUN_DIR}/vehicle-status.yaml"
readonly CLOCK_LOG="${RUN_DIR}/clock.yaml"
readonly NETWORK_LOG="${RUN_DIR}/network.txt"
readonly RESOURCE_LOG="${RUN_DIR}/resource-snapshot.txt"
readonly GZ_SERVICES_LOG="${RUN_DIR}/gazebo-services.txt"
readonly GZ_TOPICS_LOG="${RUN_DIR}/gazebo-topics.txt"
readonly TIME_CONTROL_LOG="${RUN_DIR}/time-control.json"
readonly RESOURCE_BASELINE_LOG="${RUN_DIR}/resource-baseline.json"
readonly TIME_PROBE_STDOUT="${RUN_DIR}/time-probe.stdout"
readonly RESULT_LOG="${RUN_DIR}/result.env"

for required_file in \
    "${PX4_ROOT}/build/px4_sitl_default/bin/px4" \
    "${PX4_ROOT}/Tools/simulation/gz/worlds/rover.sdf" \
    "${AGENT_BIN}" \
    "/opt/ros/jazzy/setup.bash" \
    "${PX4_MSGS_SETUP}" \
    "${PROFILE_PATH}" \
    "${TIME_PROBE}"; do
    if [[ ! -e "${required_file}" ]]; then
        echo "error: required S0 runtime input is missing: ${required_file}" >&2
        exit 2
    fi
done

mkdir -p "${RUN_DIR}"

agent_pid=""
px4_pgid=""
bridge_pgid=""

stop_process_group() {
    local pgid="${1:-}"
    if [[ -z "${pgid}" ]]; then
        return
    fi
    if kill -0 -- "-${pgid}" 2>/dev/null; then
        kill -INT -- "-${pgid}" 2>/dev/null || true
        for _ in {1..20}; do
            if ! kill -0 -- "-${pgid}" 2>/dev/null; then
                return
            fi
            sleep 0.25
        done
        kill -TERM -- "-${pgid}" 2>/dev/null || true
        sleep 1
        kill -KILL -- "-${pgid}" 2>/dev/null || true
    fi
}

cleanup() {
    set +e
    exec 3>&-
    stop_process_group "${bridge_pgid}"
    stop_process_group "${px4_pgid}"
    if [[ -n "${agent_pid}" ]] && kill -0 "${agent_pid}" 2>/dev/null; then
        kill -TERM "${agent_pid}" 2>/dev/null
        wait "${agent_pid}" 2>/dev/null
    fi
}
trap cleanup EXIT INT TERM

wait_for_pattern() {
    local file="$1"
    local pattern="$2"
    local attempts="$3"
    for ((attempt = 0; attempt < attempts; attempt += 1)); do
        if grep -Fq "${pattern}" "${file}" 2>/dev/null; then
            return 0
        fi
        sleep 0.5
    done
    return 1
}

env \
    LD_LIBRARY_PATH="${AGENT_PREFIX}/lib" \
    "${AGENT_BIN}" udp4 -p 8888 -v 4 >"${AGENT_LOG}" 2>&1 &
agent_pid="$!"
sleep 1
if ! kill -0 "${agent_pid}" 2>/dev/null; then
    echo "error: Micro XRCE-DDS Agent exited during startup" >&2
    exit 1
fi

{
    echo "network_namespace=loopback-only"
    ip -brief address
    ss -lunp
} >"${NETWORK_LOG}"

coproc PX4_PROCESS {
    exec setsid env HEADLESS=1 PX4_SIM_SPEED_FACTOR="${SPEED_FACTOR}" make -C "${PX4_ROOT}" \
        px4_sitl gz_rover_ackermann >"${PX4_LOG}" 2>&1
}
px4_pgid="${PX4_PROCESS_PID}"
exec 3>&"${PX4_PROCESS[1]}"

if ! wait_for_pattern "${PX4_LOG}" "Startup script returned successfully" 120; then
    echo "error: PX4 stock rover did not reach startup completion" >&2
    exit 1
fi

sleep 3
printf '%s\n' \
    "ver all" \
    "commander status" >&3

if ! wait_for_pattern "${AGENT_LOG}" "session established" 60 \
    || ! wait_for_pattern \
        "${PX4_LOG}" \
        "successfully created rt/fmu/out/vehicle_status_v1 data writer" \
        60; then
    echo "error: PX4 uXRCE-DDS client did not connect to the loopback agent" >&2
    exit 1
fi
printf '%s\n' "uxrce_dds_client status" >&3
if ! wait_for_pattern "${PX4_LOG}" "Running, connected" 20; then
    echo "error: PX4 did not report the established uXRCE-DDS session" >&2
    exit 1
fi
{
    echo
    echo "runtime_listeners=loopback-only-netns"
    ss -lunp
} >>"${NETWORK_LOG}"
gz service -l >"${GZ_SERVICES_LOG}"
gz topic -l >"${GZ_TOPICS_LOG}"

set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source "${PX4_MSGS_SETUP}"
set -u
export ROS_DOMAIN_ID=0
export ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST
unset ROS_LOCALHOST_ONLY

setsid ros2 run ros_gz_bridge parameter_bridge \
    '/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock' >"${BRIDGE_LOG}" 2>&1 &
bridge_pgid="$!"

timeout 20s ros2 topic list -t >"${TOPICS_LOG}"
vehicle_status_topic="/fmu/out/vehicle_status"
if grep -Fq "/fmu/out/vehicle_status_v1" "${TOPICS_LOG}"; then
    vehicle_status_topic="/fmu/out/vehicle_status_v1"
fi
timeout 20s ros2 topic echo --once \
    "${vehicle_status_topic}" px4_msgs/msg/VehicleStatus >"${VEHICLE_STATUS_LOG}"
timeout 20s ros2 topic echo --once \
    /clock rosgraph_msgs/msg/Clock >"${CLOCK_LOG}"

python3 "${TIME_PROBE}" \
    --profile "${PROFILE_PATH}" \
    --speed-factor "${SPEED_FACTOR}" \
    --time-output "${TIME_CONTROL_LOG}" \
    --resource-output "${RESOURCE_BASELINE_LOG}" \
    --storage-root "${D_ROOT}" \
    --owned-pid "${agent_pid}" \
    --owned-pgid "${px4_pgid}" \
    --owned-pgid "${bridge_pgid}" >"${TIME_PROBE_STDOUT}"

{
    echo "sample=non-acceptance-point-snapshot"
    free -b
    ps -g "${px4_pgid}" -o pid,ppid,pgid,rss,vsz,pcpu,comm,args
    ps -p "${agent_pid}" -o pid,ppid,pgid,rss,vsz,pcpu,comm,args
    ps -g "${bridge_pgid}" -o pid,ppid,pgid,rss,vsz,pcpu,comm,args
    if command -v nvidia-smi >/dev/null; then
        nvidia-smi \
            --query-gpu=memory.used,utilization.gpu \
            --format=csv,noheader,nounits
    fi
} >"${RESOURCE_LOG}"

printf '%s\n' "shutdown" >&3
if ! wait_for_pattern "${PX4_LOG}" "Exiting NOW." 20; then
    echo "error: PX4 did not acknowledge the shutdown command" >&2
    exit 1
fi

cleanup
trap - EXIT INT TERM

px4_status="fail"
dds_status="fail"
vehicle_status="fail"
clock_status="fail"
time_control_status="fail"
resource_baseline_status="fail"
lifecycle_status="fail"

grep -Fq "world: rover, model: rover_ackermann_0" "${PX4_LOG}" && px4_status="pass"
if grep -Fq "session established" "${AGENT_LOG}" \
    && grep -Fq \
        "successfully created rt/fmu/out/vehicle_status_v1 data writer" \
        "${PX4_LOG}"; then
    dds_status="pass"
fi
grep -Fq "arming_state:" "${VEHICLE_STATUS_LOG}" && vehicle_status="pass"
grep -Fq "clock:" "${CLOCK_LOG}" && clock_status="pass"
grep -Fq '"verdict": "pass"' "${TIME_CONTROL_LOG}" && time_control_status="pass"
grep -Fq '"verdict": "pass"' "${RESOURCE_BASELINE_LOG}" \
    && resource_baseline_status="pass"
if ! pgrep -f "${PX4_ROOT}/build/px4_sitl_default/bin/px4" >/dev/null \
    && ! pgrep -f "gz sim.*rover.sdf" >/dev/null \
    && ! pgrep -f "${AGENT_BIN}" >/dev/null; then
    lifecycle_status="pass"
fi

{
    echo "run_id=${RUN_ID}"
    echo "network_scope=loopback-only-netns"
    echo "actuator_authority=false"
    echo "speed_factor=${SPEED_FACTOR}"
    echo "px4_stock_rover=${px4_status}"
    echo "uxrce_dds=${dds_status}"
    echo "ros2_vehicle_status=${vehicle_status}"
    echo "gazebo_clock=${clock_status}"
    echo "pause_step_speed=${time_control_status}"
    echo "resource_baseline=${resource_baseline_status}"
    echo "clean_lifecycle=${lifecycle_status}"
} >"${RESULT_LOG}"

if [[ "${px4_status}" != "pass" \
    || "${dds_status}" != "pass" \
    || "${vehicle_status}" != "pass" \
    || "${clock_status}" != "pass" \
    || "${time_control_status}" != "pass" \
    || "${resource_baseline_status}" != "pass" \
    || "${lifecycle_status}" != "pass" ]]; then
    echo "error: one or more S0 smoke checks failed; inspect ${RUN_DIR}" >&2
    exit 1
fi

echo "SIM S0 smoke PASS: ${RUN_DIR}"
