#!/usr/bin/env bash
# Exit-asserting RM-03 regression harness for ci-queue-wait.sh.
# Every case is a process-level assertion: a classifier-only green cannot satisfy it.

set -u

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/ci-queue-wait-tristate}"
REPO_DIR="$WORK_DIR/repo"
STUB_DIR="$WORK_DIR/stubs"
AUDIT_LOG="$WORK_DIR/audit/ci-queue-wait.jsonl"
STATUS_OBSERVED="$WORK_DIR/status-observed"
CLOCK_LOG="$WORK_DIR/clock.log"
WATCHDOG_PYTHON="/usr/bin/python3"
WATCHDOG_SCRIPT="$WORK_DIR/real-clock-watchdog.py"
WATCHDOG_TIMEOUT_SEC=5
WATCHDOG_EXIT=90
FEATURE_BRANCH="fix/rm-03-fixture"

if [[ ! -x "$WATCHDOG_PYTHON" ]]; then
    echo "FAIL setup: required real-clock watchdog runtime is unavailable at $WATCHDOG_PYTHON" >&2
    exit 1
fi

rm -rf "$WORK_DIR"
mkdir -p "$REPO_DIR" "$STUB_DIR"
cat > "$WATCHDOG_SCRIPT" <<'PY'
import os
import signal
import subprocess
import sys

if len(sys.argv) < 3:
    raise SystemExit(2)

timeout_seconds = float(sys.argv[1])
process = subprocess.Popen(sys.argv[2:], start_new_session=True)
try:
    return_code = process.wait(timeout=timeout_seconds)
except subprocess.TimeoutExpired:
    try:
        os.killpg(process.pid, signal.SIGKILL)
    except ProcessLookupError:
        pass
    process.wait()
    print(
        f"FAIL HANG watchdog: subject exceeded {timeout_seconds:g}s "
        "before completing its intended path",
        file=sys.stderr,
    )
    raise SystemExit(90)

if return_code < 0:
    raise SystemExit(128 - return_code)
raise SystemExit(return_code)
PY
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" checkout -q -b "$FEATURE_BRANCH"
git -C "$REPO_DIR" remote add origin https://git.example.test/acme/widgets.git

cat > "$STUB_DIR/curl" <<'SH'
#!/usr/bin/env bash
set -euo pipefail

url=""
has_write_out=0
for arg in "$@"; do
    case "$arg" in
        -w) has_write_out=1 ;;
        http://*|https://*) url="$arg" ;;
    esac
done
printf '%s\n' "$url" >> "${MOSAIC_STUB_URL_LOG:?}"

case "$url" in
    */branches/*)
        if [[ "${MOSAIC_STUB_BRANCH_MODE:-ok}" == "hang-before-provider" ]]; then
            while :; do :; done
        fi
        if [[ "${MOSAIC_STUB_BRANCH_MODE:-ok}" == "unreachable" ]]; then
            exit 7
        fi
        body='{"commit":{"id":"deadbeefcafef00d0123456789abcdef01234567"}}'
        if [[ "$has_write_out" -eq 1 ]]; then
            printf '%s\n200' "$body"
        else
            printf '%s' "$body"
        fi
        ;;
    */status)
        : > "${MOSAIC_STUB_STATUS_OBSERVED:?}"
        case "${MOSAIC_STUB_STATUS_MODE:?}" in
            success)   printf '%s' '{"state":"success","statuses":[{"status":"success"}]}' ;;
            pending)   printf '%s' '{"state":"pending","statuses":[{"status":"pending","context":"ci/test"}]}' ;;
            failure)   printf '%s' '{"state":"failure","statuses":[{"status":"failure"}]}' ;;
            no-status) printf '%s' '{"state":"","statuses":[]}' ;;
            aggregate-success-no-status) printf '%s' '{"state":"success","statuses":[]}' ;;
            malformed) printf '%s' 'not-json' ;;
            malformed-statuses-type) printf '%s' '{"state":"success","statuses":"corrupt"}' ;;
            malformed-status-entry) printf '%s' '{"state":"success","statuses":[null]}' ;;
            unknown) printf '%s' '{"state":"success","statuses":[{"status":"cancelled"}]}' ;;
            large-success)
                python3 -c 'import json; print(json.dumps({"state":"success", "statuses":[{"status":"success"}], "padding":"x" * (160 * 1024)}), end="")'
                ;;
            unreachable) exit 7 ;;
            *) echo "unknown status mode" >&2; exit 2 ;;
        esac
        ;;
    *) echo "unexpected curl URL: $url" >&2; exit 2 ;;
esac
SH

cat > "$STUB_DIR/date" <<'SH'
#!/usr/bin/env bash
set -euo pipefail

if [[ "$#" -ne 1 || "$1" != "+%s" ]]; then
    echo "unexpected date invocation: $*" >&2
    exit 2
fi

if [[ -e "${MOSAIC_STUB_STATUS_OBSERVED:?}" ]]; then
    printf 'date-phase=after-status\n' >> "${MOSAIC_STUB_CLOCK_LOG:?}"
    printf '1002\n'
else
    printf 'date-phase=before-status\n' >> "${MOSAIC_STUB_CLOCK_LOG:?}"
    printf '1000\n'
fi
SH

cat > "$STUB_DIR/sleep" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
printf 'sleep-after-status=%s\n' "$*" >> "${MOSAIC_STUB_CLOCK_LOG:?}"
SH
chmod +x "$STUB_DIR/curl" "$STUB_DIR/date" "$STUB_DIR/sleep"

run_guard() {
    local status_mode="$1"
    local audit_log="${2:-$AUDIT_LOG}"
    shift 2 || true
    (
        cd "$REPO_DIR" || exit
        export PATH="$STUB_DIR:$PATH"
        export MOSAIC_CREDENTIALS_FILE="$WORK_DIR/no-credentials.json"
        if [[ "$status_mode" == "credential-unresolvable" ]]; then
            export HOME="$WORK_DIR/empty-home"
            mkdir -p "$HOME"
            unset GITEA_TOKEN GITEA_URL MOSAIC_GIT_IDENTITY
            export MOSAIC_STUB_STATUS_MODE=success
        else
            export GITEA_TOKEN=stub-token
            export GITEA_URL=https://git.example.test
            export MOSAIC_STUB_STATUS_MODE="$status_mode"
        fi
        rm -f "$STATUS_OBSERVED" "$CLOCK_LOG"
        export MOSAIC_STUB_URL_LOG="$WORK_DIR/urls.log"
        export MOSAIC_STUB_STATUS_OBSERVED="$STATUS_OBSERVED"
        export MOSAIC_STUB_CLOCK_LOG="$CLOCK_LOG"
        export MOSAIC_CI_QUEUE_AUDIT_LOG="$audit_log"
        # Provider observation is the synchronization event. The one-second
        # timeout is subject semantics under virtual time, never a wall wait.
        # The absolute Python runtime uses an internal monotonic wait and kills
        # the subject's isolated process group. Neither operation can resolve
        # to the virtual date/sleep stubs at the front of PATH.
        local subject_rc
        if "$WATCHDOG_PYTHON" "$WATCHDOG_SCRIPT" "$WATCHDOG_TIMEOUT_SEC" \
            "$SCRIPT_DIR/ci-queue-wait.sh" --purpose "${MOSAIC_TEST_PURPOSE:-push}" -t 1 -i 1 "$@"; then
            subject_rc=0
        else
            subject_rc=$?
        fi
        return "$subject_rc"
    )
}

failures=0
assert_provider_observed() {
    local name="$1" require_expiration="${2:-0}"
    if [[ ! -e "$STATUS_OBSERVED" ]]; then
        echo "FAIL $name: status provider was not observed" >&2
        failures=$((failures + 1))
    fi
    if [[ ! -s "$CLOCK_LOG" ]] || ! grep -q '^date-phase=before-status$' "$CLOCK_LOG"; then
        echo "FAIL $name: virtual clock interception did not run before provider observation" >&2
        failures=$((failures + 1))
    fi
    if [[ "$require_expiration" -eq 1 ]]; then
        if ! grep -q '^sleep-after-status=' "$CLOCK_LOG" || ! grep -q '^date-phase=after-status$' "$CLOCK_LOG"; then
            echo "FAIL $name: pending path did not expire after provider observation" >&2
            failures=$((failures + 1))
        fi
    fi
}

run_assertion() {
    local name="$1" expected_rc="$2" status_mode="$3" required_text="$4"
    local output rc
    shift 4
    set +e
    output=$(run_guard "$status_mode" "$AUDIT_LOG" "$@" 2>&1)
    rc=$?
    set -e

    case "$expected_rc" in
        zero)
            if [[ "$rc" -ne 0 ]]; then
                echo "FAIL $name: expected rc=0, got rc=$rc" >&2
                failures=$((failures + 1))
            fi
            ;;
        nonzero)
            if [[ "$rc" -eq 0 ]]; then
                echo "FAIL $name: expected rc!=0, got rc=0" >&2
                failures=$((failures + 1))
            fi
            ;;
        not126)
            if [[ "$rc" -eq 126 ]]; then
                echo "FAIL $name: payload transport hit ARG_MAX (rc=126)" >&2
                failures=$((failures + 1))
            fi
            ;;
    esac
    if [[ "$output" != *"$required_text"* ]]; then
        echo "FAIL $name: output missing '$required_text' (rc=$rc)" >&2
        printf '%s\n' "$output" >&2
        failures=$((failures + 1))
    fi
    if [[ "$status_mode" != "credential-unresolvable" ]]; then
        if [[ "$status_mode" == "pending" ]]; then
            assert_provider_observed "$name" 1
        else
            assert_provider_observed "$name"
        fi
    fi
}

set -e
: > "$WORK_DIR/urls.log"
# Push readiness is queue clearance, not proof that prior CI succeeded.
run_assertion push-success zero success 'state=terminal-success'
run_assertion push-pending nonzero pending 'ASSERTED_NOT_READY'
run_assertion push-failure zero failure 'queue-clear state=terminal-failure purpose=push'
run_assertion push-no-status zero no-status 'queue-clear state=no-status purpose=push'
run_assertion push-aggregate-success-no-status zero aggregate-success-no-status 'queue-clear state=no-status purpose=push'
run_assertion push-require-status nonzero no-status 'ASSERTED_NOT_READY state=no-status' --require-status
run_assertion push-malformed nonzero malformed 'ASSERTED_NOT_READY'
run_assertion push-malformed-statuses-type nonzero malformed-statuses-type 'ASSERTED_NOT_READY'
run_assertion push-malformed-status-entry nonzero malformed-status-entry 'ASSERTED_NOT_READY'
run_assertion push-unknown nonzero unknown 'ASSERTED_NOT_READY'
run_assertion push-large-payload not126 large-success 'state=terminal-success'
run_assertion credential-unresolvable zero credential-unresolvable 'CANNOT_ASSERT'
run_assertion provider-unreachable zero unreachable 'CANNOT_ASSERT'

# Merge readiness remains fail-closed and requires exact-head terminal success.
MOSAIC_TEST_PURPOSE=merge run_assertion merge-success zero success 'state=terminal-success'
MOSAIC_TEST_PURPOSE=merge run_assertion merge-failure nonzero failure 'ASSERTED_NOT_READY state=terminal-failure'
MOSAIC_TEST_PURPOSE=merge run_assertion merge-no-status nonzero no-status 'ASSERTED_NOT_READY state=no-status'
MOSAIC_TEST_PURPOSE=merge run_assertion merge-unknown nonzero unknown 'ASSERTED_NOT_READY state=unknown'

# Positive liveness control: a subject mutant hangs before the branch lookup
# can reach the status provider. Only the independent real-clock watchdog may
# terminate it, and its failure must be distinct from subject timeout rc=124.
set +e
watchdog_output=$(MOSAIC_STUB_BRANCH_MODE=hang-before-provider run_guard success "$AUDIT_LOG" 2>&1)
watchdog_rc=$?
set -e
if [[ "$watchdog_rc" -ne "$WATCHDOG_EXIT" ]]; then
    echo "FAIL watchdog-control: expected hang-specific rc=$WATCHDOG_EXIT, got rc=$watchdog_rc" >&2
    failures=$((failures + 1))
fi
if [[ "$watchdog_output" != *"FAIL HANG watchdog:"* ]]; then
    echo "FAIL watchdog-control: expected distinct hang-specific diagnostic" >&2
    printf '%s\n' "$watchdog_output" >&2
    failures=$((failures + 1))
fi
if [[ -e "$STATUS_OBSERVED" ]]; then
    echo "FAIL watchdog-control: hanging mutant unexpectedly reached the status provider" >&2
    failures=$((failures + 1))
fi

if [[ ! -s "$AUDIT_LOG" ]] || ! grep -q '"outcome":"CANNOT_ASSERT"' "$AUDIT_LOG"; then
    echo "FAIL provider-unreachable-audit: expected durable CANNOT_ASSERT JSONL record" >&2
    failures=$((failures + 1))
fi

# Merge cannot proceed without exact-head evidence. CANNOT_ASSERT is retryable exit 75,
# distinct from ASSERTED_NOT_READY (3/124), and still writes its audit record.
merge_audit_lines_before=$(wc -l < "$AUDIT_LOG")
set +e
merge_unreachable_output=$(MOSAIC_TEST_PURPOSE=merge run_guard unreachable "$AUDIT_LOG" 2>&1)
merge_unreachable_rc=$?
set -e
if [[ "$merge_unreachable_rc" -ne 75 ]]; then
    echo "FAIL merge-provider-unreachable: expected rc=75, got rc=$merge_unreachable_rc" >&2
    failures=$((failures + 1))
fi
if [[ "$merge_unreachable_output" != *"CANNOT_ASSERT"* ]]; then
    echo "FAIL merge-provider-unreachable: expected loud CANNOT_ASSERT diagnostic" >&2
    failures=$((failures + 1))
fi
assert_provider_observed merge-provider-unreachable
merge_audit_lines_after=$(wc -l < "$AUDIT_LOG")
if [[ "$merge_audit_lines_after" -le "$merge_audit_lines_before" ]]; then
    echo "FAIL merge-provider-unreachable: expected an additional audit record" >&2
    failures=$((failures + 1))
fi

# A feature-branch push with no -B must inspect the checked-out feature branch.
if ! grep -q "/branches/$FEATURE_BRANCH" "$WORK_DIR/urls.log"; then
    echo "FAIL implicit-branch: provider was not queried for $FEATURE_BRANCH" >&2
    failures=$((failures + 1))
fi

# Merge callers can pin both a fork repository and the exact reviewed head SHA.
exact_sha=0123456789abcdef0123456789abcdef01234567
: > "$WORK_DIR/urls.log"
run_assertion exact-fork-head zero success 'state=terminal-success' \
    -B fix/rm-03-fixture -R contributor/widgets-fork --sha "$exact_sha"
if ! grep -q "/repos/contributor/widgets-fork/commits/$exact_sha/status" "$WORK_DIR/urls.log"; then
    echo "FAIL exact-fork-head: status URL did not bind fork repository and exact SHA" >&2
    failures=$((failures + 1))
fi
if grep -q '/branches/' "$WORK_DIR/urls.log"; then
    echo "FAIL exact-fork-head: explicit SHA must not be re-resolved through a branch" >&2
    failures=$((failures + 1))
fi

# Platform/repository discovery failures use the same audited CANNOT_ASSERT path.
audit_lines_before=$(wc -l < "$AUDIT_LOG")
git -C "$REPO_DIR" remote set-url origin https://gitlab.com/acme/widgets.git
set +e
unsupported_output=$(run_guard success "$AUDIT_LOG" 2>&1)
unsupported_rc=$?
set -e
git -C "$REPO_DIR" remote set-url origin https://git.example.test/acme/widgets.git
if [[ "$unsupported_rc" -ne 0 ]]; then
    echo "FAIL unsupported-platform: expected degraded rc=0, got rc=$unsupported_rc" >&2
    failures=$((failures + 1))
fi
if [[ "$unsupported_output" != *"CANNOT_ASSERT"* ]]; then
    echo "FAIL unsupported-platform: expected loud CANNOT_ASSERT diagnostic" >&2
    failures=$((failures + 1))
fi
audit_lines_after=$(wc -l < "$AUDIT_LOG")
if [[ "$audit_lines_after" -le "$audit_lines_before" ]]; then
    echo "FAIL unsupported-platform: expected an additional audit record" >&2
    failures=$((failures + 1))
fi

# A degraded pass is forbidden if the audit receipt cannot be written.
mkdir -p "$WORK_DIR/not-a-directory"
printf 'file' > "$WORK_DIR/not-a-directory/parent"
set +e
audit_failure_output=$(run_guard unreachable "$WORK_DIR/not-a-directory/parent/audit.jsonl" 2>&1)
audit_failure_rc=$?
set -e
if [[ "$audit_failure_rc" -eq 0 ]]; then
    echo "FAIL audit-unavailable: expected rc!=0, got rc=0" >&2
    failures=$((failures + 1))
fi
if [[ "$audit_failure_output" != *"audit"* ]]; then
    echo "FAIL audit-unavailable: expected loud audit failure diagnostic" >&2
    failures=$((failures + 1))
fi
assert_provider_observed audit-unavailable

if [[ "$failures" -ne 0 ]]; then
    echo "ci-queue-wait tri-state regression failed ($failures assertions)" >&2
    exit 1
fi

echo "ci-queue-wait tri-state regression passed (all outcome classes)"
