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

# Test timeout via CLI flag (global timeout for entire run)
cat <<EOF >mise.toml
[tasks.task1]
run = "sleep 1 && echo 'Task 1 done'"

[tasks.task2]
run = "sleep 1 && echo 'Task 2 done'"

[tasks.task3]
run = "sleep 5 && echo 'Task 3 should not appear'"
EOF

echo "Testing global timeout via CLI flag..."
output="$(mise run --timeout=3s task1 ::: task2 ::: task3 2>&1 || true)"
if [[ $output != *"timed out"* ]]; then
  echo "ERROR: Expected 'timed out' in output, got: $output"
  exit 1
fi
echo "✓ CLI flag global timeout works"

# A killed task is reparented, and a zombie still answers `kill -0` until
# something reaps it; containers often run an init that never does.
process_running() {
  local stat
  stat="$(ps -o stat= -p "$1" 2>/dev/null)" || return 1
  [[ $stat != Z* ]]
}

# The global timeout must not leave the running task behind. Output goes to a
# file: a leaked task holding a pipe would keep the command substitution waiting
# until the task ended on its own. Friendly errors exit through
# `exit::kill_all`; test the run's own cleanup instead.
assert_timeout_stops_task() {
  rm -f task.pid
  MISE_FRIENDLY_ERROR=0 mise run --timeout=5s stuck >timeout.log 2>&1 || true
  if ! grep -q "timed out" timeout.log; then
    echo "ERROR: Expected 'timed out' in output, got: $(cat timeout.log)"
    exit 1
  fi
  if [[ ! -f task.pid ]]; then
    echo "ERROR: the task did not start before the timeout"
    exit 1
  fi
  local task_pid
  task_pid="$(cat task.pid)"
  for _ in $(seq 50); do
    process_running "$task_pid" || break
    sleep 0.1
  done
  if process_running "$task_pid"; then
    kill -9 "$task_pid"
    echo "ERROR: task process $task_pid outlived mise run --timeout"
    exit 1
  fi
}

# The task ignores SIGTERM (inherited through exec), so this covers the SIGKILL
# after the grace period.
cat <<'EOF' >mise.toml
[tasks.stuck]
run = "trap '' TERM; echo $$ > task.pid; exec sleep 60"
EOF
echo "Testing global timeout stops the running task..."
assert_timeout_stops_task
echo "✓ Global timeout stops the running task"

# The shell exits on SIGTERM, but its background child ignores it and has let go
# of mise's pipes. The SIGKILL must still reach the child's process group.
cat <<'EOF' >mise.toml
[tasks.stuck]
run = "(trap '' TERM; exec sleep 60) >/dev/null 2>&1 & echo $! > task.pid; wait"
EOF
echo "Testing global timeout stops a background child that ignores SIGTERM..."
assert_timeout_stops_task
echo "✓ Global timeout stops a background child that ignores SIGTERM"

# Test timeout via task config (individual task timeout)
cat <<EOF >mise.toml
[tasks.quick]
run = "echo 'Quick task'"
timeout = "5s"

[tasks.slow]
run = "sleep 2 && echo 'Slow task done'"
timeout = "5s"

[tasks.too_slow]
run = "sleep 10 && echo 'Should not appear'"
timeout = "1s"
EOF

echo "Testing individual task timeout (too_slow should time out)..."
output="$(mise run too_slow 2>&1 || true)"
if [[ $output != *"timed out"* ]]; then
  echo "ERROR: Expected 'timed out' in output, got: $output"
  exit 1
fi
echo "✓ Individual task timeout works"

echo "Testing task completes within timeout..."
mise run quick 2>&1
mise run slow 2>&1
echo "✓ Tasks within timeout complete successfully"

# A task that exits 0 on SIGTERM still timed out.
cat <<'EOF' >mise.toml
[tasks.clean_exit]
run = "trap 'exit 0' TERM; while true; do sleep 1; done"
timeout = "2s"
EOF

echo "Testing a task that exits cleanly on SIGTERM still times out..."
if output="$(mise run clean_exit 2>&1)"; then
  echo "ERROR: Expected clean_exit to fail, got: $output"
  exit 1
fi
if [[ $output != *"timed out"* ]]; then
  echo "ERROR: Expected 'timed out' in output, got: $output"
  exit 1
fi
echo "✓ A task that exits cleanly on SIGTERM still times out"

# Test timeout via environment variable (global)
cat <<EOF >mise.toml
[tasks.env_test1]
run = "sleep 1 && echo 'Task 1'"

[tasks.env_test2]
run = "sleep 2 && echo 'Task 2'"
EOF

echo "Testing global timeout via environment variable..."
output="$(MISE_TASK_TIMEOUT=2s mise run env_test1 ::: env_test2 2>&1 || true)"
if [[ $output != *"timed out"* ]]; then
  echo "ERROR: Expected 'timed out' in output, got: $output"
  exit 1
fi
echo "✓ Environment variable global timeout works"

# Test task timeout is independent per task
cat <<EOF >mise.toml
[tasks.parallel1]
run = "sleep 2 && echo 'Parallel 1 done'"
timeout = "3s"

[tasks.parallel2]
run = "sleep 2 && echo 'Parallel 2 done'"
timeout = "3s"
EOF

echo "Testing parallel tasks with individual timeouts..."
mise run parallel1 ::: parallel2 2>&1
echo "✓ Parallel tasks with individual timeouts work"

# Test using duration formats
cat <<EOF >mise.toml
[tasks.duration_test]
run = "sleep 1 && echo 'Duration test done'"
timeout = "2s"
EOF

echo "Testing duration format parsing..."
mise run duration_test 2>&1
echo "✓ Duration format works"

# Test task timeout Tera rendering with the ordinary task-load context
cat <<'EOF' >mise.toml
[tasks.template_timeout]
run = "sleep 5 && echo 'Should not appear'"
timeout = "{{ vars.timeout }}"

[tasks.template_timeout.vars]
timeout = "1s"
EOF

echo "Testing task timeout Tera rendering..."
output="$(mise run template_timeout 2>&1 || true)"
if [[ $output != *"timed out"* ]]; then
  echo "ERROR: Expected rendered task timeout to fire, got: $output"
  exit 1
fi
echo "✓ Task timeout Tera rendering works"

# Test global timeout + per-task timeout serve distinct purposes
cat <<EOF >mise.toml
[settings]
task_timeout = "10s"

[tasks.quick_task]
run = "sleep 1 && echo 'Quick done'"
timeout = "5s"

[tasks.slow_task]
run = "sleep 8 && echo 'Slow done'"
timeout = "3s"
EOF

echo "Testing global + per-task timeout interaction..."
output="$(mise run quick_task ::: slow_task 2>&1 || true)"
if [[ $output != *"timed out"* ]]; then
  echo "ERROR: Expected per-task timeout to fire, got: $output"
  exit 1
fi
if [[ $output != *"Quick done"* ]]; then
  echo "ERROR: quick_task should have completed, got: $output"
  exit 1
fi
echo "✓ Global + per-task timeout interaction works"

echo "All timeout tests passed!"
