#!/usr/bin/env bash
set -euo pipefail

if [[ "${WRKR_PERF_BUDGETS_CLEAN_ENV:-0}" != "1" ]]; then
  # GitHub's CodeQL action leaves tracer variables in the job environment after
  # init. Command-latency budgets must measure Wrkr itself, not CodeQL tracing.
  for name in "${!CODEQL_@}" "${!SEMMLE_@}"; do
    unset "$name"
  done
  unset LD_PRELOAD
  export WRKR_PERF_BUDGETS_CLEAN_ENV=1
  exec "$0" "$@"
fi

if ! command -v python3 >/dev/null 2>&1; then
  echo "python3 is required for benchmark budget validation" >&2
  exit 7
fi

bench_output="$(mktemp)"
trap 'rm -f "$bench_output"' EXIT

go test -run '^$' -bench 'Benchmark(ScoreComputeDeterministic|RiskScoreDeterministic)$' -benchmem -count=5 ./core/score ./core/risk | tee "$bench_output"

python3 - "$bench_output" <<'PY'
import json
import math
import pathlib
import re
import shutil
import statistics
import subprocess
import sys
import tempfile
import time

output_path = pathlib.Path(sys.argv[1])
root = pathlib.Path('.')
bench_contract = json.loads((root / 'perf' / 'bench_baseline.json').read_text(encoding='utf-8'))
runtime_contract = json.loads((root / 'perf' / 'runtime_slo_budgets.json').read_text(encoding='utf-8'))
bench_entries = {item['name']: item for item in bench_contract['benchmarks']}

pattern = re.compile(r'^(Benchmark\S+)\s+\d+\s+([0-9]+(?:\.[0-9]+)?)\s+ns/op')
observed = {}
for line in output_path.read_text(encoding='utf-8').splitlines():
    match = pattern.match(line.strip())
    if not match:
        continue
    name = re.sub(r'-\d+$', '', match.group(1))
    value = float(match.group(2))
    observed.setdefault(name, []).append(value)

errors = []
for name, contract in bench_entries.items():
    values = observed.get(name, [])
    if not values:
        errors.append(f'missing benchmark output: {name}')
        continue
    median = statistics.median(values)
    limit = float(contract['median']) * float(contract['max_regression_factor'])
    if median > limit:
        errors.append(f'{name} median {median:.2f}ns/op exceeds limit {limit:.2f}ns/op')

wrkr_bin = root / '.tmp' / 'wrkr'
wrkr_bin.parent.mkdir(parents=True, exist_ok=True)
# Always rebuild the CLI under test so perf gates measure the current workspace,
# not a stale binary left behind by a previous run.
subprocess.run(['go', 'build', '-o', str(wrkr_bin), './cmd/wrkr'], cwd=root, check=True)


def timed_run(cmd):
    start = time.perf_counter()
    proc = subprocess.run(cmd, cwd=root, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True)
    duration = time.perf_counter() - start
    return proc.returncode, duration, proc.stderr.strip()


def p95_ms(values):
    ordered = sorted(values)
    if not ordered:
        return 0.0
    if len(ordered) == 1:
        return ordered[0] * 1000.0
    rank = 0.95 * (len(ordered) - 1)
    lower = int(math.floor(rank))
    upper = int(math.ceil(rank))
    if lower == upper:
        return ordered[lower] * 1000.0
    fraction = rank - lower
    return (ordered[lower] + (ordered[upper] - ordered[lower]) * fraction) * 1000.0


def sample_command_runs(name, cmd, runs=5):
    durations = []
    for _ in range(runs):
        rc, duration, stderr = timed_run(cmd)
        if rc != 0:
            return [], f'{name} command failed with exit {rc}: {stderr}'
        durations.append(duration)
    return durations, ''


def enforce_command_budget(name, cmd, limit_ms, windows=2, runs_per_window=5):
    # Measure in independent windows so a single noisy burst on shared CI runners
    # does not create a false positive while sustained regressions still fail.
    observed = []
    for _ in range(windows):
        runs, sample_err = sample_command_runs(name, cmd, runs=runs_per_window)
        if sample_err:
            errors.append(sample_err)
            return
        measured = p95_ms(runs)
        observed.append(measured)
        if measured <= limit_ms:
            return
    errors.append(f'{name} p95 {min(observed):.2f}ms exceeds budget {limit_ms:.2f}ms after {windows} windows')


tmpdir = pathlib.Path(tempfile.mkdtemp(prefix='wrkr-perf-'))
try:
    # Keep scan workloads in separate roots so proof-chain paths do not overlap.
    # Commands below are budgeted on the 100-repo state, so its chain must not
    # include records emitted by the 500-repo scan.
    workspace100 = tmpdir / 'workload-100'
    workspace500 = tmpdir / 'workload-500'
    repos100 = workspace100 / 'repos'
    repos500 = workspace500 / 'repos'
    for i in range(100):
        (repos100 / f'repo-{i:03d}').mkdir(parents=True, exist_ok=True)
    for i in range(500):
        (repos500 / f'repo-{i:03d}').mkdir(parents=True, exist_ok=True)

    state100 = workspace100 / 'state.json'
    state500 = workspace500 / 'state.json'

    rc, duration100, stderr100 = timed_run([str(wrkr_bin), 'scan', '--path', str(repos100), '--state', str(state100), '--json'])
    if rc != 0:
        errors.append(f'scan 100 repos failed with exit {rc}: {stderr100}')
    else:
        budget100 = float(runtime_contract['scan']['repos_100']['max_seconds'])
        if duration100 > budget100:
            errors.append(f'scan 100 repos took {duration100:.2f}s (budget {budget100:.2f}s)')

    rc, duration500, stderr500 = timed_run([str(wrkr_bin), 'scan', '--path', str(repos500), '--state', str(state500), '--json'])
    if rc != 0:
        errors.append(f'scan 500 repos failed with exit {rc}: {stderr500}')
    else:
        budget500 = float(runtime_contract['scan']['repos_500']['max_seconds'])
        if duration500 > budget500:
            errors.append(f'scan 500 repos took {duration500:.2f}s (budget {budget500:.2f}s)')

    baseline = workspace100 / 'baseline.json'
    rc, _, stderr = timed_run([str(wrkr_bin), 'regress', 'init', '--baseline', str(state100), '--output', str(baseline), '--json'])
    if rc != 0:
        errors.append(f'regress init failed with exit {rc}: {stderr}')

    # The scan/setup phase above performs substantial filesystem work. Let the
    # system settle before measuring short-lived command latency so the budgets
    # reflect steady-state command responsiveness rather than immediate
    # post-setup I/O carryover on shared or thermally constrained runners.
    time.sleep(10)

    command_budgets = runtime_contract.get('commands', {})

    enforce_command_budget(
        'score',
        [str(wrkr_bin), 'score', '--state', str(state100), '--json'],
        float(command_budgets['score']['p95_ms']),
    )
    enforce_command_budget(
        'verify',
        [str(wrkr_bin), 'verify', '--chain', '--state', str(state100), '--json'],
        float(command_budgets['verify_chain']['p95_ms']),
    )
    enforce_command_budget(
        'regress run',
        [str(wrkr_bin), 'regress', 'run', '--baseline', str(baseline), '--state', str(state100), '--json'],
        float(command_budgets['regress_run']['p95_ms']),
    )
finally:
    shutil.rmtree(tmpdir)

if errors:
    for err in errors:
        print(err, file=sys.stderr)
    sys.exit(1)

print('benchmark and runtime budgets: pass')
PY

WRKR_ENTERPRISE_PRESSURE_SCORECARD_DIR=.tmp/release WRKR_ENTERPRISE_PRESSURE_ENFORCE_TIMINGS=1 \
  go test ./internal/scenarios -run '^TestScenarioWave42EnterprisePressureContract$' -tags=scenario -count=1 -timeout="${GO_TEST_TIMEOUT:-20m}"
