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

###############################################################################
# Claude Code Digest Orchestrator
#
# Completion signal (STRICT, audit-friendly):
#   - COMPLETED-DAYS.md contains today's ISO date (YYYY-MM-DD) as a full line
#   - AND COMPLETED-DAYS.md mtime is within FRESHNESS_SECONDS (default: 2 hours)
#
# Policy:
#   - Run at 21:00 via systemd timer (recommended)
#   - Start Claude, wait up to 20 minutes (polling for completion)
#   - If not complete, kill Claude process group, retry up to MAX_ATTEMPTS
#   - If complete, kill Claude (if still running), write ack, exit success
#
# Logging (forensic grade):
#   - logs/raw/              : raw logs (run log + per-attempt Claude logs)
#   - logs/past-logs/DATE.md : structured Markdown report per day
#
# Testing:
#   - Run manually anytime (no need to wait for timer)
#   - Use --simulate to test without calling Claude (no token spend)
#   - Use --attempt-timeout-seconds to speed up tests
###############################################################################

# ----------------------------
# Defaults (override via CLI flags)
# ----------------------------
WORKDIR="/workspace/digest"
MAX_ATTEMPTS=4
ATTEMPT_TIMEOUT_SECONDS=$((20 * 60))
POLL_SECONDS=5
FRESHNESS_SECONDS=$((2 * 60 * 60))
KILL_GRACE_SECONDS=10

IGNORE_ACK=0
SIMULATE=0
DATE_ISO_OVERRIDE=""
VPN_HEALTH_SCRIPT=""
VPN_HEALTH_FAILURE_WAIT_SECONDS=60

# Ensure claude/node are discoverable under systemd/cron (customize if needed)
export PATH="/home/agent/.local/bin:/home/agent/.nvm/versions/node/v22.12.0/bin:/usr/local/bin:/usr/bin:/bin:${PATH}"
export HOME="${HOME:-/home/agent}"

usage() {
  cat <<'EOF'
Usage:
  run-digest-orchestrator.sh [options]

Options:
  --workdir PATH                  Working directory containing CLAUDE.md
  --max-attempts N                Default: 4
  --attempt-timeout-seconds N     Default: 1200 (20m)
  --poll-seconds N                Default: 5
  --freshness-seconds N           Default: 7200 (2h)
  --kill-grace-seconds N          Default: 10
  --date-iso YYYY-MM-DD           Override "today" (useful for testing)
  --vpn-health-script PATH        Override vpn-health.sh path
  --vpn-health-failure-wait N     Wait before retrying after failed VPN preflight (default: 60)
  --ignore-ack                    Run even if .orchestrator-ack-YYYY-MM-DD exists
  --simulate                      Don't run Claude. Run a dummy long sleep instead (zero tokens).
  -h, --help                      Show help

Examples:
  # Normal manual run (uses real Claude)
  ./run-digest-orchestrator.sh --ignore-ack

  # Fast test (real Claude, 60s window)
  ./run-digest-orchestrator.sh --ignore-ack --attempt-timeout-seconds 60 --poll-seconds 1

  # Zero-token test (simulate), 15s timeout per attempt
  ./run-digest-orchestrator.sh --simulate --ignore-ack --attempt-timeout-seconds 15 --poll-seconds 1
EOF
}

# ----------------------------
# Parse args
# ----------------------------
while [[ $# -gt 0 ]]; do
  case "$1" in
    --workdir) WORKDIR="$2"; shift 2 ;;
    --max-attempts) MAX_ATTEMPTS="$2"; shift 2 ;;
    --attempt-timeout-seconds) ATTEMPT_TIMEOUT_SECONDS="$2"; shift 2 ;;
    --poll-seconds) POLL_SECONDS="$2"; shift 2 ;;
    --freshness-seconds) FRESHNESS_SECONDS="$2"; shift 2 ;;
    --kill-grace-seconds) KILL_GRACE_SECONDS="$2"; shift 2 ;;
    --date-iso) DATE_ISO_OVERRIDE="$2"; shift 2 ;;
    --vpn-health-script) VPN_HEALTH_SCRIPT="$2"; shift 2 ;;
    --vpn-health-failure-wait) VPN_HEALTH_FAILURE_WAIT_SECONDS="$2"; shift 2 ;;
    --ignore-ack) IGNORE_ACK=1; shift ;;
    --simulate) SIMULATE=1; shift ;;
    -h|--help) usage; exit 0 ;;
    *) echo "ERROR: Unknown option: $1"; usage; exit 2 ;;
  esac
done

# ----------------------------
# Basic sanity checks
# ----------------------------
if [[ ! -d "$WORKDIR" ]]; then
  echo "ERROR: WORKDIR does not exist: $WORKDIR"
  exit 1
fi

cd "$WORKDIR"

if [[ ! -f "${WORKDIR}/CLAUDE.md" ]]; then
  echo "ERROR: Missing ${WORKDIR}/CLAUDE.md"
  exit 1
fi

if [[ -z "$VPN_HEALTH_SCRIPT" ]]; then
  VPN_HEALTH_SCRIPT="${WORKDIR}/vpn-health.sh"
fi

if [[ ! -x "$VPN_HEALTH_SCRIPT" ]]; then
  echo "ERROR: Missing executable VPN health script: ${VPN_HEALTH_SCRIPT}"
  exit 1
fi

# Commands we require (Linux/GNU)
require_cmd() {
  command -v "$1" >/dev/null 2>&1 || {
    echo "ERROR: Missing required command: $1"
    exit 127
  }
}

require_cmd date
require_cmd stat
require_cmd tee
require_cmd flock
require_cmd setsid
require_cmd grep
require_cmd tr
require_cmd tail
require_cmd sed
require_cmd hostname

if [[ "$SIMULATE" -eq 0 ]]; then
  require_cmd claude
fi

# ----------------------------
# Date handling
# ----------------------------
if [[ -n "$DATE_ISO_OVERRIDE" ]]; then
  DATE_ISO="$DATE_ISO_OVERRIDE"
else
  DATE_ISO="$(date +'%Y-%m-%d')"
fi

# Convert ISO date to human readable (GNU date expected)
DATE_HUMAN="$(date -d "$DATE_ISO" +'%B %d, %Y')"

# ----------------------------
# Files and directories
# ----------------------------
COMPLETED_FILE="${WORKDIR}/COMPLETED-DAYS.md"
ACK_FILE="${WORKDIR}/.orchestrator-ack-${DATE_ISO}"

LOGROOT="${WORKDIR}/logs"
RAW_DIR="${LOGROOT}/raw"
PAST_DIR="${LOGROOT}/past-logs"
mkdir -p "$RAW_DIR" "$PAST_DIR"

RUN_STAMP="$(date +'%Y-%m-%d_%H-%M-%S')"
RUN_RAW_LOG="${RAW_DIR}/run_${DATE_ISO}_${RUN_STAMP}.log"
REPORT_FILE="${PAST_DIR}/${DATE_ISO}.md"
HEALTH_RAW_LOG="${RAW_DIR}/vpn_health_${DATE_ISO}_${RUN_STAMP}.log"

# Redirect all orchestrator stdout/stderr to raw run log (and console if manual)
exec > >(tee -a "$RUN_RAW_LOG") 2>&1

HOST="$(hostname -f 2>/dev/null || hostname)"
USER_NAME="$(whoami)"

# ----------------------------
# Markdown helpers
# ----------------------------
md_init_if_needed() {
  if [[ ! -f "$REPORT_FILE" ]]; then
    cat > "$REPORT_FILE" <<EOF
# Digest Orchestrator Report — ${DATE_ISO}

This report is auto-generated. It contains **per-run summaries** and pointers to raw logs in \`logs/raw/\`.

**Completion source of truth:** \`${COMPLETED_FILE}\` (append-only)
A day is considered complete when:
- \`${COMPLETED_FILE}\` contains \`${DATE_ISO}\` as a full line
- AND the file was modified within the last ${FRESHNESS_SECONDS} seconds

EOF
  fi
}

md_append() {
  # Append exactly what you pass (supports multi-line)
  printf "%s\n" "$*" >> "$REPORT_FILE"
}

fmt_ts() {
  # Human-friendly timestamp
  date -Is
}

fmt_duration() {
  local s="$1"
  local h=$((s / 3600))
  local m=$(((s % 3600) / 60))
  local sec=$((s % 60))
  printf "%dh %dm %ds" "$h" "$m" "$sec"
}

ledger_line_present_today() {
  [[ -f "$COMPLETED_FILE" ]] || return 1
  tr -d '\r' < "$COMPLETED_FILE" | grep -Fxq "$DATE_ISO"
}

ledger_age_seconds() {
  [[ -f "$COMPLETED_FILE" ]] || { echo "-1"; return 0; }
  local mtime now age
  mtime="$(stat -c '%Y' "$COMPLETED_FILE")"
  now="$(date +%s)"
  age=$((now - mtime))
  (( age >= 0 )) || age=0
  echo "$age"
}

ledger_is_fresh() {
  local age
  age="$(ledger_age_seconds)"
  [[ "$age" -ge 0 ]] && (( age <= FRESHNESS_SECONDS ))
}

is_completed_strict() {
  ledger_line_present_today && ledger_is_fresh
}

report_completion_debug() {
  local exists="no" line="no" age="-1" fresh="no"
  [[ -f "$COMPLETED_FILE" ]] && exists="yes"
  ledger_line_present_today && line="yes" || true
  age="$(ledger_age_seconds)"
  ledger_is_fresh && fresh="yes" || true
  md_append "- **Ledger exists:** ${exists}"
  md_append "- **Today's date line present:** ${line}"
  md_append "- **Ledger age (seconds):** ${age}"
  md_append "- **Ledger is fresh (<= ${FRESHNESS_SECONDS}s):** ${fresh}"
}

md_embed_tail_indented() {
  local file="$1"
  local lines="$2"
  if [[ ! -f "$file" ]]; then
    md_append "    (no log file found: $file)"
    return 0
  fi
  tail -n "$lines" "$file" | sed 's/^/    /' >> "$REPORT_FILE"
}

# ----------------------------
# Locking (prevents overlap)
# ----------------------------
LOCKFILE="/tmp/claude-digest-orchestrator.lock"
exec 9>"$LOCKFILE"
if ! flock -n 9; then
  echo "Another orchestrator run is already active. Exiting."
  exit 0
fi

# ----------------------------
# Start report section for this run
# ----------------------------
md_init_if_needed

RUN_START_EPOCH="$(date +%s)"
md_append ""
md_append "---"
md_append ""
md_append "## Orchestrator Run — ${RUN_STAMP}"
md_append ""
md_append "- **Started:** $(fmt_ts)"
md_append "- **Host:** ${HOST}"
md_append "- **User:** ${USER_NAME}"
md_append "- **Workdir:** \`${WORKDIR}\`"
md_append "- **Date (ISO):** \`${DATE_ISO}\`"
md_append "- **Date (Human):** \`${DATE_HUMAN}\`"
md_append "- **Ledger:** \`${COMPLETED_FILE}\`"
md_append "- **Raw run log:** \`${RUN_RAW_LOG}\`"
md_append "- **VPN health log:** \`${HEALTH_RAW_LOG}\`"
md_append "- **Config:** max_attempts=${MAX_ATTEMPTS}, attempt_timeout=${ATTEMPT_TIMEOUT_SECONDS}s, poll=${POLL_SECONDS}s, freshness=${FRESHNESS_SECONDS}s"
md_append "- **Mode:** $([[ "$SIMULATE" -eq 1 ]] && echo "SIMULATE (no Claude tokens)" || echo "REAL Claude")"
md_append ""

echo "================================================================="
echo "RUN START: $(fmt_ts)"
echo "WORKDIR: $WORKDIR"
echo "DATE_ISO: $DATE_ISO"
echo "DATE_HUMAN: $DATE_HUMAN"
echo "RAW RUN LOG: $RUN_RAW_LOG"
echo "VPN HEALTH LOG: $HEALTH_RAW_LOG"
echo "REPORT: $REPORT_FILE"
echo "COMPLETED FILE: $COMPLETED_FILE"
echo "ACK FILE: $ACK_FILE"
echo "================================================================="

# If ack exists and not ignoring, exit immediately
if [[ -f "$ACK_FILE" && "$IGNORE_ACK" -eq 0 ]]; then
  md_append "### Early Exit"
  md_append ""
  md_append "- **Reason:** Ack file already exists: \`${ACK_FILE}\`"
  md_append "- **Status:** ✅ SUCCESS (already completed)"
  md_append ""
  exit 0
fi

# If already complete (strict) right now, write ack and exit
if is_completed_strict; then
  md_append "### Early Exit"
  md_append ""
  md_append "- **Reason:** STRICT completion already satisfied at start."
  md_append ""
  report_completion_debug
  printf "ok %s %s\n" "$DATE_ISO" "$(fmt_ts)" > "$ACK_FILE"
  md_append ""
  md_append "- **Ack written:** \`${ACK_FILE}\`"
  md_append "- **Status:** ✅ SUCCESS"
  md_append ""
  exit 0
fi

# ----------------------------
# Process management helpers
# ----------------------------
CURRENT_PID=""
LAST_KILL_SUMMARY=""

kill_process_group() {
  local pid="$1"
  LAST_KILL_SUMMARY="(no action)"

  if [[ -z "${pid:-}" ]]; then
    LAST_KILL_SUMMARY="(no pid)"
    return 0
  fi

  if ! kill -0 "$pid" 2>/dev/null; then
    LAST_KILL_SUMMARY="already not running"
    return 0
  fi

  echo "Stopping process group PID/PGID=$pid ..."
  kill -TERM -- "-$pid" 2>/dev/null || true

  local waited=0
  while kill -0 "$pid" 2>/dev/null; do
    if (( waited >= KILL_GRACE_SECONDS )); then
      echo "Escalating to SIGKILL for process group -$pid"
      kill -KILL -- "-$pid" 2>/dev/null || true
      LAST_KILL_SUMMARY="SIGTERM -> SIGKILL"
      return 0
    fi
    sleep 1
    waited=$((waited + 1))
  done

  LAST_KILL_SUMMARY="SIGTERM (exited in ${waited}s)"
}

cleanup_on_signal() {
  md_append ""
  md_append "### Orchestrator Terminated"
  md_append ""
  md_append "- **Time:** $(fmt_ts)"
  md_append "- **Reason:** Received termination signal"
  if [[ -n "${CURRENT_PID:-}" ]]; then
    kill_process_group "$CURRENT_PID"
    md_append "- **Action:** Killed process group ${CURRENT_PID} (${LAST_KILL_SUMMARY})"
  fi
  md_append ""
  exit 130
}
trap cleanup_on_signal INT TERM

start_worker() {
  local prompt="$1"
  local attempt="$2"
  local attempt_log="$3"

  # Write a small header into the attempt log
  {
    echo "== Attempt ${attempt} =="
    echo "Started: $(fmt_ts)"
    echo "Workdir: ${WORKDIR}"
    echo "Mode: $([[ "$SIMULATE" -eq 1 ]] && echo "SIMULATE" || echo "REAL")"
    echo "Prompt:"
    echo "$prompt"
    echo ""
  } >> "$attempt_log"

  cd "$WORKDIR"

  if [[ "$SIMULATE" -eq 1 ]]; then
    # Dummy long-running process for zero-token testing
    setsid bash -lc "sleep 100000" >> "$attempt_log" 2>&1 &
  else
    setsid claude --dangerously-skip-permissions --max-turns 200 -p "$prompt" >> "$attempt_log" 2>&1 &
  fi

  local pid=$!
  echo "$pid"
}

wait_for_completion_or_timeout() {
  local pid="$1"
  local deadline=$(( $(date +%s) + ATTEMPT_TIMEOUT_SECONDS ))

  while true; do
    if is_completed_strict; then
      return 0  # completed
    fi

    if ! kill -0 "$pid" 2>/dev/null; then
      return 1  # worker exited early
    fi

    local now
    now="$(date +%s)"
    if (( now >= deadline )); then
      return 2  # timeout
    fi

    sleep "$POLL_SECONDS"
  done
}

run_vpn_healthcheck() {
  local context="$1"
  local status=0

  echo "Running VPN health check (${context}) ..."
  echo "== $(fmt_ts) :: ${context} ==" >> "$HEALTH_RAW_LOG"
  "$VPN_HEALTH_SCRIPT" >> "$HEALTH_RAW_LOG" 2>&1 || status=$?
  echo "" >> "$HEALTH_RAW_LOG"
  return "$status"
}

attempt_log_has_network_error() {
  local attempt_log="$1"
  [[ -f "$attempt_log" ]] || return 1

  grep -Eiq 'ConnectionRefused|Connection error|Unable to connect to API|Could not resolve|timed out|timeout' "$attempt_log"
}

# ----------------------------
# Prompt (minimal + includes today's date)
# ----------------------------
PROMPT="Read CLAUDE.md and generate the digest for ${DATE_HUMAN}. Follow all instructions in CLAUDE.md exactly. When fully complete, append today's ISO date (${DATE_ISO}) as a new line to COMPLETED-DAYS.md."

# ----------------------------
# Attempt loop
# ----------------------------
FINAL_STATUS="FAILURE"
attempt=1

while (( attempt <= MAX_ATTEMPTS )); do
  ATTEMPT_START_EPOCH="$(date +%s)"
  ATTEMPT_LOG="${RAW_DIR}/claude_attempt${attempt}_${DATE_ISO}_${RUN_STAMP}.log"

  md_append "### Attempt ${attempt}"
  md_append ""
  md_append "- **Started:** $(fmt_ts)"
  md_append "- **Attempt timeout:** ${ATTEMPT_TIMEOUT_SECONDS}s"
  md_append "- **Attempt raw log:** \`${ATTEMPT_LOG}\`"
  md_append ""

  echo "---- Attempt ${attempt}/${MAX_ATTEMPTS} ----"
  echo "Preflight VPN health check before worker start"

  set +e
  run_vpn_healthcheck "attempt-${attempt}-preflight"
  health_status=$?
  set -e

  if [[ $health_status -ne 0 ]]; then
    ATTEMPT_END_EPOCH="$(date +%s)"
    ATTEMPT_DURATION=$((ATTEMPT_END_EPOCH - ATTEMPT_START_EPOCH))

    md_append "- **VPN health preflight:** ❌ FAILED"
    md_append "- **Ended:** $(fmt_ts)"
    md_append "- **Duration:** $(fmt_duration "$ATTEMPT_DURATION")"
    md_append "- **Result:** ❌ Preflight failed (VPN/connectivity unhealthy)"
    md_append ""
    md_append "#### VPN Health Log Tail (last 80 lines)"
    md_append ""
    md_embed_tail_indented "$HEALTH_RAW_LOG" 80
    md_append ""

    echo "VPN health preflight failed before attempt ${attempt}"
    sleep "$VPN_HEALTH_FAILURE_WAIT_SECONDS"
    attempt=$((attempt + 1))
    continue
  fi

  md_append "- **VPN health preflight:** ✅ OK"
  echo "Starting worker. Attempt log: $ATTEMPT_LOG"

  pid="$(start_worker "$PROMPT" "$attempt" "$ATTEMPT_LOG")"
  CURRENT_PID="$pid"

  md_append "- **PID/PGID:** \`${pid}\`"

  echo "Worker PID/PGID: $pid"
  echo "Waiting for completion (strict) up to ${ATTEMPT_TIMEOUT_SECONDS}s..."

  set +e
  wait_for_completion_or_timeout "$pid"
  result=$?
  set -e

  ATTEMPT_END_EPOCH="$(date +%s)"
  ATTEMPT_DURATION=$((ATTEMPT_END_EPOCH - ATTEMPT_START_EPOCH))

  md_append "- **Ended:** $(fmt_ts)"
  md_append "- **Duration:** $(fmt_duration "$ATTEMPT_DURATION")"

  if [[ $result -eq 0 ]]; then
    md_append "- **Result:** ✅ COMPLETED (strict)"
    md_append ""
    md_append "#### Completion Debug"
    report_completion_debug
    md_append ""
    md_append "#### Action"
    kill_process_group "$pid"
    md_append "- **Killed worker:** ${LAST_KILL_SUMMARY}"
    md_append ""

    printf "ok %s %s\n" "$DATE_ISO" "$(fmt_ts)" > "$ACK_FILE"
    md_append "- **Ack written:** \`${ACK_FILE}\`"

    # Include a tail excerpt of Claude output for quick debugging
    md_append ""
    md_append "#### Claude Attempt Log Tail (last 120 lines)"
    md_append ""
    md_embed_tail_indented "$ATTEMPT_LOG" 120
    md_append ""

    FINAL_STATUS="SUCCESS"
    break
  fi

  if [[ $result -eq 1 ]]; then
    md_append "- **Result:** ❌ Worker exited early (no completion detected)"
  else
    md_append "- **Result:** ❌ TIMEOUT (no completion detected)"
  fi

  md_append ""
  md_append "#### Completion Debug"
  report_completion_debug
  md_append ""

  md_append "#### Action"
  kill_process_group "$pid"
  md_append "- **Killed worker:** ${LAST_KILL_SUMMARY}"
  md_append ""

  if attempt_log_has_network_error "$ATTEMPT_LOG"; then
    md_append "#### Network Recovery"
    set +e
    run_vpn_healthcheck "attempt-${attempt}-postfailure"
    health_status=$?
    set -e
    if [[ $health_status -eq 0 ]]; then
      md_append "- **Post-failure VPN recovery:** ✅ OK"
    else
      md_append "- **Post-failure VPN recovery:** ❌ FAILED"
    fi
    md_append ""
  fi

  md_append "#### Claude Attempt Log Tail (last 120 lines)"
  md_append ""
  md_embed_tail_indented "$ATTEMPT_LOG" 120
  md_append ""

  CURRENT_PID=""
  attempt=$((attempt + 1))
done

RUN_END_EPOCH="$(date +%s)"
RUN_DURATION=$((RUN_END_EPOCH - RUN_START_EPOCH))

md_append "## Final Outcome"
md_append ""
if [[ "$FINAL_STATUS" == "SUCCESS" ]]; then
  md_append "- **Status:** ✅ SUCCESS"
else
  md_append "- **Status:** ❌ FAILURE"
fi
md_append "- **Attempts used:** $((attempt > MAX_ATTEMPTS ? MAX_ATTEMPTS : attempt)) / ${MAX_ATTEMPTS}"
md_append "- **Total elapsed:** $(fmt_duration "$RUN_DURATION")"
md_append "- **Raw run log:** \`${RUN_RAW_LOG}\`"
md_append ""

echo "================================================================="
echo "RUN END: $(fmt_ts)"
echo "STATUS: $FINAL_STATUS"
echo "REPORT: $REPORT_FILE"
echo "================================================================="

if [[ "$FINAL_STATUS" == "SUCCESS" ]]; then
  exit 0
fi

exit 1
