#!/usr/bin/env bash
# scripts/verify-install-edge.sh
#
# One-command hardware verification for the DPF installer + Edge Node
# end-to-end path on Linux and macOS. Produces one tarball + one
# paste-able summary so an operator can run a single command and file
# the result against the install_verification.md issue template.
#
# Covers verification ledger rows 1, 2, 3, and 4 in a single sweep:
#   1. Installer ran successfully (Outcome 1/2: hardware verification)
#   2. Portal /api/health responds (Outcome 2: Linux / macOS end-to-end)
#   3. Prometheus targets are scrape-healthy (Outcome 3: observability)
#   4. Edge Node enrolls + heartbeats + submits a discovery run
#      (Outcome 4: Edge Node end-to-end)
#
# On macOS (Darwin) the script additionally checks:
#   - Architecture (arm64 = Apple Silicon = supported)
#   - LaunchAgent autostart registration
#   - Docker Desktop Model Runner reachability
#
# Designed to be safe to run on a host that already has DPF installed
# (default), OR to bootstrap a fresh host first (--bootstrap).
#
# Output: $DPF_STATE_DIR/verify-bundle-<timestamp>.tar.gz with:
#   - env-fingerprint.txt    (uname, sw_vers / os-release, docker/node/pnpm versions)
#   - install-state.json     (redacted)
#   - install-doctor/        (extracted doctor bundle for context)
#   - prom-targets.json      (Prometheus targets API snapshot)
#   - edge-lifecycle.log     (stdout from verify-lifecycle.ts)
#   - summary.md             (paste-able for the GitHub issue)
#
# Bash 3.2 baseline (macOS stock /bin/bash).

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# Source the shared logging helpers from the installer surface.
# shellcheck source=scripts/installer/lib/logging.sh
. "$REPO_ROOT/scripts/installer/lib/logging.sh"
# shellcheck source=scripts/installer/lib/state.sh
. "$REPO_ROOT/scripts/installer/lib/state.sh"

DPF_VERIFY_BOOTSTRAP=0
DPF_VERIFY_NO_TEARDOWN=1   # default: leave state running so operator can inspect
DPF_VERIFY_AUTHORITY_URL="${DPF_AUTHORITY_URL:-http://localhost:3000}"
DPF_VERIFY_PROMETHEUS_URL="${DPF_PROMETHEUS_URL:-http://localhost:9090}"

usage() {
  cat <<EOF
Open Digital Product Factory — Hardware Verification Wrapper

Usage:
  bash scripts/verify-install-edge.sh [flags]

Flags:
  --bootstrap        Run \`install-dpf.sh --headless --release --no-autostart\`
                     first. Use this on a fresh host that doesn't yet have
                     DPF installed. Default: assume DPF is already up.
  --authority-url U  Authority Core URL. Default: \$DPF_AUTHORITY_URL or
                     http://localhost:3000.
  --prom-url U       Prometheus URL. Default: \$DPF_PROMETHEUS_URL or
                     http://localhost:9090.
  --teardown         Run uninstall-dpf.sh --purge --yes --headless after
                     verification. Default: leave state for inspection.
  -h, --help         Show this help.

Output:
  Writes a single tarball to \$HOME/.dpf/verify-bundle-<timestamp>.tar.gz
  and prints a paste-able summary on stdout. Attach the tarball to a
  GitHub issue using the install_verification.md template.

Spec: docs/superpowers/specs/2026-05-09-dpf-edge-node-design.md
Roadmap: docs/superpowers/plans/2026-05-12-edge-node-phase0-roadmap.md
EOF
}

while [ $# -gt 0 ]; do
  case "$1" in
    --bootstrap)        DPF_VERIFY_BOOTSTRAP=1 ;;
    --authority-url)    shift; DPF_VERIFY_AUTHORITY_URL="${1:?--authority-url requires a value}" ;;
    --prom-url)         shift; DPF_VERIFY_PROMETHEUS_URL="${1:?--prom-url requires a value}" ;;
    --teardown)         DPF_VERIFY_NO_TEARDOWN=0 ;;
    -h|--help)          usage; exit 0 ;;
    *)
      echo "verify-install-edge.sh: unknown argument: $1" >&2
      echo "Run 'bash scripts/verify-install-edge.sh --help' for usage." >&2
      exit 64
      ;;
  esac
  shift
done

cd "$REPO_ROOT"

TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
STATE_DIR="$(dpf_state_dir)"
mkdir -p "$STATE_DIR"
WORK_DIR="$(mktemp -d)"
TARBALL="$STATE_DIR/verify-bundle-${TIMESTAMP}.tar.gz"

# Track pass/fail per step in a structured form so the summary
# at the end is machine-readable (operators can grep STATUS=).
SUMMARY_FILE="$WORK_DIR/summary.md"
{
  echo "# DPF Hardware Verification Report"
  echo
  echo "**Timestamp:** $TIMESTAMP"
  echo "**Generated by:** scripts/verify-install-edge.sh"
  echo
} > "$SUMMARY_FILE"

declare -i STEP_PASS=0
declare -i STEP_FAIL=0

record_step() {
  local name="$1"
  local status="$2"     # PASS | FAIL | SKIP
  local detail="${3:-}"
  case "$status" in
    PASS) STEP_PASS=$((STEP_PASS + 1)) ;;
    FAIL) STEP_FAIL=$((STEP_FAIL + 1)) ;;
  esac
  {
    echo "- **$name:** \`$status\`${detail:+ — $detail}"
  } >> "$SUMMARY_FILE"
}

cleanup_workdir() {
  if [ -d "$WORK_DIR" ]; then
    rm -rf "$WORK_DIR"
  fi
}
trap cleanup_workdir EXIT

# ── Step 1: environment fingerprint ───────────────────────────────────────
step "Capturing host fingerprint"
{
  echo "## uname -a"
  uname -a 2>&1 || true
  echo
  if [ -r /etc/os-release ]; then
    echo "## /etc/os-release"
    cat /etc/os-release 2>&1 || true
    echo
  fi
  if [ "$(uname -s)" = "Darwin" ]; then
    echo "## sw_vers"
    sw_vers 2>&1 || true
    echo
  fi
  echo "## docker / docker compose"
  docker --version 2>&1 || echo "docker not available"
  docker compose version 2>&1 || echo "docker compose not available"
  echo
  echo "## node / pnpm"
  node -v 2>&1 || echo "node not available"
  pnpm -v 2>&1 || echo "pnpm not available"
  echo
  echo "## installer version"
  grep "^DPF_INSTALLER_VERSION" install-dpf.sh 2>&1 || echo "install-dpf.sh not found"
} > "$WORK_DIR/env-fingerprint.txt" 2>&1
ok "Fingerprint captured ($(wc -l < "$WORK_DIR/env-fingerprint.txt") lines)"

# ── Step 1b: macOS-specific checks (Darwin only) ──────────────────────────
if [ "$(uname -s)" = "Darwin" ]; then
  step "macOS-specific checks"

  # Architecture — Apple Silicon (arm64) is the supported target.
  ARCH="$(uname -m)"
  if [ "$ARCH" = "arm64" ]; then
    ok "Architecture: arm64 (Apple Silicon)"
    record_step "Architecture (macOS)" "PASS" "arm64 — Apple Silicon"
  else
    warn "Architecture: $ARCH — Intel Macs run under Rosetta with a performance penalty"
    record_step "Architecture (macOS)" "FAIL" "$ARCH — pass --force-unsupported-host to install-dpf.sh if needed"
  fi

  # LaunchAgent autostart (installed by default; skip with --no-autostart).
  LAUNCH_AGENT_PLIST="$HOME/Library/LaunchAgents/local.dpf-autostart.plist"
  if [ -f "$LAUNCH_AGENT_PLIST" ]; then
    if launchctl list 2>/dev/null | grep -q "local.dpf-autostart"; then
      ok "LaunchAgent loaded: local.dpf-autostart"
      record_step "LaunchAgent autostart (macOS)" "PASS" "plist present and loaded by launchctl"
    else
      warn "LaunchAgent plist present but not loaded by launchctl"
      record_step "LaunchAgent autostart (macOS)" "FAIL" "run: launchctl load '$LAUNCH_AGENT_PLIST'"
    fi
  else
    warn "LaunchAgent not found at $LAUNCH_AGENT_PLIST"
    record_step "LaunchAgent autostart (macOS)" "SKIP" "plist missing — was --no-autostart used?"
  fi

  # Docker Desktop Model Runner (requires Docker Desktop ≥ 4.40).
  # SKIP rather than FAIL if unreachable — LLM_BASE_URL may point to an external provider.
  MODEL_RUNNER_URL="http://model-runner.docker.internal/engines/v1/models"
  if curl --silent --max-time 5 --fail "$MODEL_RUNNER_URL" -o /dev/null 2>/dev/null; then
    ok "Docker Model Runner reachable"
    record_step "Docker Model Runner (macOS)" "PASS"
  else
    warn "Docker Model Runner not reachable — OK if LLM_BASE_URL is set to an external provider"
    record_step "Docker Model Runner (macOS)" "SKIP" "unreachable at $MODEL_RUNNER_URL"
  fi

  # Note the known bridge-mode limitation in the fingerprint for context.
  {
    echo
    echo "## macOS Edge Node note"
    echo "Edge Node runs inside Docker Desktop's Linux VM (bridge mode)."
    echo "Discovery sees VM interfaces (172.x.x.x), not your Mac's real NICs."
    echo "This is expected until the native macOS Edge Node binary (T3) ships."
  } >> "$WORK_DIR/env-fingerprint.txt"
fi

# ── Step 2: optional install ──────────────────────────────────────────────
if [ "$DPF_VERIFY_BOOTSTRAP" = "1" ]; then
  step "Bootstrapping fresh DPF install"
  if bash install-dpf.sh --headless --release --no-autostart 2>&1 | tee "$WORK_DIR/install.log" >/dev/null; then
    ok "install-dpf.sh --headless --release --no-autostart succeeded"
    record_step "Install (bootstrap)" "PASS"
  else
    fail_msg="install-dpf.sh exited non-zero; see install.log"
    warn "$fail_msg"
    record_step "Install (bootstrap)" "FAIL" "$fail_msg"
    # Continue — partial bundle is still useful for debugging.
  fi
else
  record_step "Install (bootstrap)" "SKIP" "operator pre-installed; pass --bootstrap to re-run"
fi

# ── Step 3: portal /api/health ────────────────────────────────────────────
step "Polling portal /api/health"
HEALTH_URL="${DPF_VERIFY_AUTHORITY_URL%/}/api/health"
if curl --silent --max-time 10 --fail "$HEALTH_URL" -o "$WORK_DIR/health.json" 2>&1; then
  ok "Portal healthy at $HEALTH_URL"
  record_step "Portal /api/health" "PASS"
else
  warn "Portal at $HEALTH_URL did not return 200"
  record_step "Portal /api/health" "FAIL" "Authority Core unreachable; ensure dpf-start.sh has run"
  # Without a portal, no further step is meaningful. Skip rest.
  echo "Aborting: portal unreachable. See env-fingerprint.txt and install.log." >&2
  cp "$WORK_DIR/env-fingerprint.txt" "$STATE_DIR/" 2>/dev/null || true
  exit 1
fi

# ── Step 4: Prometheus targets (Outcome 3 — observability) ────────────────
step "Checking Prometheus scrape targets"
PROM_URL="${DPF_VERIFY_PROMETHEUS_URL%/}/api/v1/targets"
if curl --silent --max-time 10 --fail "$PROM_URL" -o "$WORK_DIR/prom-targets.json" 2>/dev/null; then
  # Count active vs down targets via grep (avoid requiring jq).
  TOTAL=$(grep -o '"health":"[a-z]*"' "$WORK_DIR/prom-targets.json" 2>/dev/null | wc -l | tr -d ' ')
  UP=$(grep -o '"health":"up"' "$WORK_DIR/prom-targets.json" 2>/dev/null | wc -l | tr -d ' ')
  DOWN=$(grep -o '"health":"down"' "$WORK_DIR/prom-targets.json" 2>/dev/null | wc -l | tr -d ' ')
  if [ "$TOTAL" -gt 0 ] && [ "$DOWN" = "0" ]; then
    ok "Prometheus has $UP/$TOTAL targets healthy"
    record_step "Prometheus scrape targets" "PASS" "$UP/$TOTAL up"
  elif [ "$TOTAL" -gt 0 ]; then
    warn "Prometheus has $UP/$TOTAL targets up, $DOWN down"
    record_step "Prometheus scrape targets" "FAIL" "$UP/$TOTAL up, $DOWN down — see prom-targets.json"
  else
    warn "Prometheus targets response had no health entries"
    record_step "Prometheus scrape targets" "FAIL" "empty response — Prometheus not yet scraping?"
  fi
else
  warn "Prometheus targets endpoint unreachable at $PROM_URL"
  record_step "Prometheus scrape targets" "SKIP" "Prometheus not exposed on host port; observability check skipped"
fi

# ── Step 5: issue bootstrap token ─────────────────────────────────────────
step "Issuing bootstrap token for Edge Node verification"
BOOTSTRAP_TOKEN=""
if BOOTSTRAP_TOKEN="$(pnpm --filter web exec tsx scripts/issue-edge-bootstrap-token.ts --ttl-minutes 30 2>"$WORK_DIR/issue-token.stderr" | tail -1)"; then
  if [ -z "$BOOTSTRAP_TOKEN" ] || [[ "$BOOTSTRAP_TOKEN" != dpfboot_* ]]; then
    record_step "Issue bootstrap token" "FAIL" "unexpected output; see issue-token.stderr"
    BOOTSTRAP_TOKEN=""
  else
    ok "Bootstrap token issued ($(echo "$BOOTSTRAP_TOKEN" | cut -c1-12)…)"
    record_step "Issue bootstrap token" "PASS"
  fi
else
  record_step "Issue bootstrap token" "FAIL" "issue-edge-bootstrap-token.ts exited non-zero"
fi

# ── Step 6: Edge Node lifecycle verification (#528) ───────────────────────
if [ -n "$BOOTSTRAP_TOKEN" ]; then
  step "Running Edge Node lifecycle verification"
  if DPF_AUTHORITY_URL="$DPF_VERIFY_AUTHORITY_URL" \
     DPF_BOOTSTRAP_TOKEN="$BOOTSTRAP_TOKEN" \
     pnpm exec tsx services/edge-node/scripts/verify-lifecycle.ts \
     >"$WORK_DIR/edge-lifecycle.log" 2>&1; then
    ok "Edge Node lifecycle verification passed"
    record_step "Edge Node lifecycle (enroll → heartbeat → discovery-run)" "PASS"
  else
    warn "Edge Node lifecycle verification failed; see edge-lifecycle.log"
    record_step "Edge Node lifecycle (enroll → heartbeat → discovery-run)" "FAIL"
  fi
else
  record_step "Edge Node lifecycle" "SKIP" "no bootstrap token; cannot run #528 lifecycle script"
fi

# ── Step 7: doctor bundle ────────────────────────────────────────────────
step "Capturing diagnostic bundle"
if bash install-dpf.sh doctor >"$WORK_DIR/doctor.stdout" 2>"$WORK_DIR/doctor.stderr"; then
  DOCTOR_TARBALL="$(grep -oE "$STATE_DIR/doctor-[0-9TZ]*\.tar\.gz" "$WORK_DIR/doctor.stdout" | tail -1)"
  if [ -n "$DOCTOR_TARBALL" ] && [ -f "$DOCTOR_TARBALL" ]; then
    mkdir -p "$WORK_DIR/install-doctor"
    tar -xzf "$DOCTOR_TARBALL" -C "$WORK_DIR/install-doctor" 2>/dev/null || true
    ok "Doctor bundle captured ($(basename "$DOCTOR_TARBALL"))"
    record_step "Doctor diagnostic bundle" "PASS" "$(basename "$DOCTOR_TARBALL")"
  else
    record_step "Doctor diagnostic bundle" "FAIL" "doctor ran but tarball path not parseable"
  fi
else
  record_step "Doctor diagnostic bundle" "FAIL" "install-dpf.sh doctor exited non-zero"
fi

# ── Optional: teardown ───────────────────────────────────────────────────
if [ "$DPF_VERIFY_NO_TEARDOWN" = "0" ]; then
  step "Tearing down (--teardown)"
  if bash uninstall-dpf.sh --purge --yes --headless >"$WORK_DIR/teardown.log" 2>&1; then
    ok "Teardown succeeded"
    record_step "Teardown" "PASS"
  else
    record_step "Teardown" "FAIL" "uninstall-dpf.sh --purge exited non-zero"
  fi
fi

# ── Compose summary + tarball ────────────────────────────────────────────
{
  echo
  echo "## Verdict"
  echo
  echo "- Passed: $STEP_PASS"
  echo "- Failed: $STEP_FAIL"
  echo
  if [ "$STEP_FAIL" -eq 0 ]; then
    echo "**✓ All verification steps passed.** This run is suitable for "
    echo "attaching to a GitHub issue using the install_verification.md "
    echo "template. The bundle at the path below contains everything a "
    echo "maintainer needs."
  else
    echo "**✗ Some verification steps failed.** Attach the bundle anyway —"
    echo "failure reports are equally valuable. Maintainers will look at the"
    echo "captured logs to identify the root cause."
  fi
  echo
  echo "## Bundle"
  echo
  echo "\`\`\`"
  echo "$TARBALL"
  echo "\`\`\`"
  echo
  echo "Attach this file to your GitHub issue. Secrets in the doctor bundle"
  echo "are auto-redacted, but review env-fingerprint.txt before sending"
  echo "if the host is sensitive."
} >> "$SUMMARY_FILE"

tar -czf "$TARBALL" -C "$WORK_DIR" . 2>/dev/null
chmod 0600 "$TARBALL"

# Print the summary to stdout for the operator.
echo
echo "─────────────────────────────────────────────────────────────────────"
cat "$SUMMARY_FILE"
echo "─────────────────────────────────────────────────────────────────────"
echo

if [ "$STEP_FAIL" -eq 0 ]; then
  ok "Verification complete — bundle at $TARBALL"
  exit 0
else
  warn "Verification completed with $STEP_FAIL failure(s) — bundle at $TARBALL"
  exit 1
fi
