#!/usr/bin/env bash
# Drive and narrate the Hermes code-proposal demo.
#
# This is the proposal loop: a build fails on a real defect, Ariadne asks
# Hermes for a minimal patch, validates it as data, pushes a branch and opens a
# pull request. Nothing merges. The point of this half is the stop, not the fix.
#
#   hermes_code_demo.sh monitor    # follow the Test Automation Diagram live
#   hermes_code_demo.sh reset      # restore the demo repository to pre-run state
#   hermes_code_demo.sh preflight  # confirm the lab is ready to demo
#   hermes_code_demo.sh run        # seed the defect and narrate the loop
#   hermes_code_demo.sh status     # current incident/alert state, no changes
#
# The autonomous-repair demo is a separate script: hermes_triage_demo.sh.
#
# FIRST RUN: copy hermes_demo.env.example to hermes_demo.env in this directory
# and fill it in. That file is git-ignored precisely so it can hold real
# tokens; this script sources it automatically. You also need bstein/
# hermes-code-demo cloned locally (default ~/Development/hermes-code-demo,
# override with CODE_REPO_DIR).
#
# The only thing this mutates is the demo repository: it pushes a seeded defect
# to master and reverts it on reset.
set -euo pipefail

# shellcheck source=scripts/ops/hermes_demo_lib.sh
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hermes_demo_lib.sh"

# DEMO_REPOS is the whole blast radius and is deliberately explicit: a real
# service's issues are genuine triage records, and clearing them to tidy a demo
# would destroy the evidence the system exists to produce.
DEMO_REPOS="${DEMO_REPOS:-hermes-code-demo}"

cmd_reset() {
  say "Reset — restoring the code demo to its pre-run state"

  if [ -z "${GITEA_TOKEN:-}" ]; then
    note "GITEA_TOKEN unset; skipping repository cleanup"
  else
    for repo in $DEMO_REPOS; do
      note "clearing bstein/$repo (demo repository)"
      local items
      items="$(gitea_get "/api/v1/repos/bstein/$repo/issues?state=all&limit=100" |
        python3 -c 'import json,sys
for i in json.load(sys.stdin):
    print(i["number"], "pr" if i.get("pull_request") else "issue")' 2>/dev/null || true)"
      if [ -z "$items" ]; then
        note "  no issues or pull requests"
      else
        while read -r num kind; do
          [ -z "$num" ] && continue
          curl -s --max-time 25 -o /dev/null -X DELETE -H "Authorization: token $GITEA_TOKEN" \
            "$GITEA_URL/api/v1/repos/bstein/$repo/issues/$num"
          note "  deleted $kind #$num"
        done <<< "$items"
      fi

      local branches
      branches="$(gitea_get "/api/v1/repos/bstein/$repo/branches" |
        python3 -c 'import json,sys,urllib.parse
for b in json.load(sys.stdin):
    if b["name"].startswith("hermes-repair/"):
        print(urllib.parse.quote(b["name"], safe=""))' 2>/dev/null || true)"
      if [ -z "$branches" ]; then
        note "  no repair branches"
      else
        for ref in $branches; do
          curl -s --max-time 25 -o /dev/null -X DELETE -H "Authorization: token $GITEA_TOKEN" \
            "$GITEA_URL/api/v1/repos/bstein/$repo/branches/$ref"
          note "  deleted branch $(printf '%b' "${ref//%/\\x}")"
        done
      fi
    done
  fi

  # The demo seeds its defect by pushing to master, and the fix only lands if
  # someone merges the pull request - which, by design, nobody does during a
  # demo. So master stays broken, and the next run aborts on "defect already
  # present". Reset has to undo the seed rather than just report it, or the
  # second demo of the day fails before it starts.
  if [ -d "$CODE_REPO_DIR/.git" ]; then
    note "restoring the demo repository working state"
    ( cd "$CODE_REPO_DIR" && git checkout -q master && git fetch -q origin &&
      git reset -q --hard origin/master ) || note "  could not sync master"
    if grep -q 'percent / 100' "$CODE_REPO_DIR/src/discount.py" 2>/dev/null; then
      note "  src/discount.py is correct; demo is armable"
    else
      note "  src/discount.py carries the seeded defect; reverting it on master"
      ( cd "$CODE_REPO_DIR" &&
        python3 - <<'PY'
import pathlib, re, sys

path = pathlib.Path("src/discount.py")
source = path.read_text()
# Matches the seeded `percent / 10` without also matching a correct
# `percent / 100`, so re-running reset on a healthy file changes nothing.
fixed = re.sub(r"percent / 10(?!\d)", "percent / 100", source)
if fixed == source:
    sys.exit("unrecognised defect; fix src/discount.py by hand")
path.write_text(fixed)
PY
        git commit -qam "revert: restore the discount divisor" && git push -q origin master &&
        note "  reverted and pushed; demo is armable" ) || note "  revert failed — fix src/discount.py by hand"
    fi
  else
    note "demo repository not cloned at $CODE_REPO_DIR; skipping"
  fi

  say "Ready"
  note "real service repositories were not touched"
  note "run 'preflight' next, then 'run'"
}

cmd_preflight() {
  require_jenkins
  say "Preflight — code demo"
  if [ -d "$CODE_REPO_DIR/.git" ]; then
    if grep -q 'percent / 100' "$CODE_REPO_DIR/src/discount.py" 2>/dev/null; then
      note "demo repository: src/discount.py is correct; armable"
    else
      note "demo repository: src/discount.py carries a defect — run 'reset' first"
    fi
  else
    note "demo repository: NOT CLONED at $CODE_REPO_DIR"
  fi
  local open_prs
  open_prs="$(gitea_get "/api/v1/repos/bstein/hermes-code-demo/pulls?state=open" 2>/dev/null |
    python3 -c 'import json,sys; print(len(json.load(sys.stdin)))' 2>/dev/null || echo '?')"
  note "open hermes-code-demo PRs: $open_prs (must be 0 — the duplicate guard refuses while one is open)"
  note "code proposals enabled: $(kubectl -n maintenance exec deploy/ariadne -c ariadne -- printenv ARIADNE_HERMES_CODE_ENABLED 2>/dev/null)"
  note "fix categories: $(kubectl -n maintenance exec deploy/ariadne -c ariadne -- printenv ARIADNE_HERMES_FIX_CATEGORIES 2>/dev/null)"
  shared_preflight
}

cmd_status() {
  shared_status
  say "Open proposals"
  note "https://scm.bstein.dev/bstein/hermes-code-demo/pulls"
}

cmd_run() {
  require_jenkins
  [ -d "$CODE_REPO_DIR/.git" ] || { echo "clone bstein/hermes-code-demo to $CODE_REPO_DIR first" >&2; exit 1; }
  local start_num next_num
  start_num="$(last_build_number "$CODE_JOB")"
  next_num=$((start_num + 1))

  say "Seeding a one-line defect in src/discount.py"
  ( cd "$CODE_REPO_DIR" && git checkout -q master && git pull -q &&
    python3 - <<'PY'
import pathlib
p = pathlib.Path("src/discount.py")
s = p.read_text()
old, new = "percent / 100", "percent / 10"
if old not in s:
    raise SystemExit("defect already present or file changed; run reset first")
p.write_text(s.replace(old, new))
PY
  )

  # Shown before the push, not after: the audience should watch the defect go
  # in rather than take on trust that the later diagnosis matched it. It is
  # also the only moment in the whole demo where a human changes any code.
  say "The change about to be pushed"
  note "$ git diff -- src/discount.py"
  ( cd "$CODE_REPO_DIR" && git --no-pager diff --unified=2 -- src/discount.py ) |
    sed 's/^/        /'

  ( cd "$CODE_REPO_DIR" &&
    git commit -qam "refactor: simplify discount percentage math" && git push -q origin master )
  note "pushed $(cd "$CODE_REPO_DIR" && git rev-parse --short HEAD) to master"
  note "a plausible-looking change that breaks three regression tests"

  say "Running the test gate -> build #$next_num"
  note "HTTP $(jenkins_post "/job/$CODE_JOB/build")"
  note "result: $(wait_for_build "$CODE_JOB" "$next_num")"

  say "Asking Ariadne to look now rather than on its next minute"
  note "\$ POST /api/internal/hermes/autotriage/run"
  note "$(poke_ariadne)"

  say "Ariadne collects evidence and asks Hermes for a minimal patch"
  note "Hermes returns an anchored patch as data; Ariadne validates path, size,"
  note "changed lines, and that the anchor is unique, then pushes hermes-repair/$next_num"
  for _ in $(seq 1 40); do
    sleep 15
    ariadne_ticks 300 1 | grep -q "code_fix_proposed" && break
  done
  ariadne_ticks 400 3

  say "Pull request awaiting human review (nothing merges automatically)"
  note "https://scm.bstein.dev/bstein/hermes-code-demo/pulls"
}

case "${1:-}" in
  run|code) cmd_run ;;
  status) cmd_status ;;
  preflight) cmd_preflight ;;
  reset) cmd_reset ;;
  monitor) run_monitor "$CODE_JOB" ;;
  *) sed -n '2,23p' "$0" | sed 's/^# \{0,1\}//' ; exit 1 ;;
esac
