#!/usr/bin/env bash
#
# jira-publish.sh
#
# Publishes an analysis / report body to a Jira issue without destroying what
# is already there. Spec: refs/analysis/render.md (Phase 4 Jira target) and
# refs/channels/jira.md (transport + emoticon escaping).
#
# Why this exists: the analysis Jira target used to PUT the description field
# directly, so an issue whose description held the analyst's original
# requirement text lost it with no preview, no backup and no undo. A comment
# cannot destroy anything, so `comment` is the default target; writing the
# description is opt-in, always backed up first, appends by default, and
# refuses to replace a non-empty description without an explicit confirmation.
#
# Usage:
#   jira-publish.sh --issue KEY --body-file FILE
#                   [--target comment|description]   (default: comment)
#                   [--mode append|replace]          (description only, default: append)
#                   [--confirm-overwrite]            (required for mode=replace on a non-empty field)
#                   [--backup-dir DIR]
#                   [--dry-run]
#
# Resolution:
#   host    JIRA_HOST, else prefs .global.hosts.jira
#   token   JIRA_TOKEN, else credential-store.sh get <prefs .global.keychainMapping.jira>
#   The token never reaches argv or a log: it is passed to curl through a -K
#   config on process substitution, the same idiom as post-pr-review.sh.
#
# Every body is run through scripts/jira-wiki-escape.mjs before it is sent, so
# Jira cannot manufacture an emoticon out of a Swift selector or a table cell.
#
# Exit codes:
#   0  published (or, with --dry-run, previewed)
#   3  refused: replace would destroy a non-empty description and no
#      --confirm-overwrite was given. The backup is already on disk.
#   4  host or token could not be resolved
#   5  a Jira API call failed
#  64  usage error

set -euo pipefail

SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
PREFS="${MULTI_AGENT_PREFS:-$HOME/.claude/multi-agent-preferences.json}"
CURL="${JIRA_PUBLISH_CURL:-curl}"

ISSUE=""
BODY_FILE=""
TARGET="comment"
MODE="append"
CONFIRM=0
DRY_RUN=0
BACKUP_DIR="${JIRA_BACKUP_DIR:-$HOME/.claude/logs/multi-agent/jira-backups}"

die() { echo "$1" >&2; exit "${2:-64}"; }

while [ "$#" -gt 0 ]; do
  case "$1" in
    --issue) ISSUE="${2:-}"; shift 2 ;;
    --body-file) BODY_FILE="${2:-}"; shift 2 ;;
    --target) TARGET="${2:-}"; shift 2 ;;
    --mode) MODE="${2:-}"; shift 2 ;;
    --backup-dir) BACKUP_DIR="${2:-}"; shift 2 ;;
    --confirm-overwrite) CONFIRM=1; shift ;;
    --dry-run) DRY_RUN=1; shift ;;
    -h|--help) sed -n '2,40p' "$0"; exit 0 ;;
    *) die "unknown argument: $1" ;;
  esac
done

[ -n "$ISSUE" ] || die "usage: jira-publish.sh --issue KEY --body-file FILE [--target comment|description]"
[ -n "$BODY_FILE" ] || die "usage: jira-publish.sh --issue KEY --body-file FILE [--target comment|description]"
[ -f "$BODY_FILE" ] || die "body file not found: $BODY_FILE"
case "$TARGET" in comment|description) ;; *) die "--target must be comment or description, got: $TARGET" ;; esac
case "$MODE" in append|replace) ;; *) die "--mode must be append or replace, got: $MODE" ;; esac

ESCAPER=""
for cand in "$SELF_DIR/../scripts/jira-wiki-escape.mjs" "$HOME/.claude/scripts/jira-wiki-escape.mjs"; do
  [ -f "$cand" ] && { ESCAPER="$cand"; break; }
done
[ -n "$ESCAPER" ] || die "jira-wiki-escape.mjs not found next to lib/ or in ~/.claude/scripts" 4

HOST="${JIRA_HOST:-}"
if [ -z "$HOST" ] && [ -f "$PREFS" ]; then
  HOST=$(jq -r '.global.hosts.jira // empty' "$PREFS" 2>/dev/null || echo "")
fi
[ -n "$HOST" ] || die "no Jira host: set JIRA_HOST or prefs .global.hosts.jira" 4
HOST="${HOST#https://}"; HOST="${HOST#http://}"; HOST="${HOST%/}"

TOKEN="${JIRA_TOKEN:-}"
if [ -z "$TOKEN" ]; then
  key="${JIRA_TOKEN_KEY:-}"
  if [ -z "$key" ] && [ -f "$PREFS" ]; then
    key=$(jq -r '.global.keychainMapping.jira // empty' "$PREFS" 2>/dev/null || echo "")
  fi
  [ -n "$key" ] || die "no Jira token: set JIRA_TOKEN or map prefs .global.keychainMapping.jira" 4
  TOKEN=$("$SELF_DIR/credential-store.sh" get "$key" 2>/dev/null || echo "")
  [ -n "$TOKEN" ] || die "Jira token not in the credential store under: $key" 4
fi

auth_cfg() { printf 'header = "Authorization: Bearer %s"\n' "$1"; }
api() { "$CURL" -sS -m 30 -K <(auth_cfg "$TOKEN") -H "Content-Type: application/json" "$@"; }

WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

node "$ESCAPER" "$BODY_FILE" > "$WORK/new.txt"

if [ "$TARGET" = "comment" ]; then
  if [ "$DRY_RUN" = "1" ]; then
    echo "dry-run: would POST a comment to $ISSUE ($(wc -l < "$WORK/new.txt" | tr -d ' ') lines)"
    cat "$WORK/new.txt"
    exit 0
  fi
  jq -n --rawfile body "$WORK/new.txt" '{body: $body}' > "$WORK/payload.json"
  http=$(api -o "$WORK/resp.json" -w '%{http_code}' -X POST \
    --data-binary @"$WORK/payload.json" \
    "https://$HOST/rest/api/2/issue/$ISSUE/comment") || http="000"
  case "$http" in
    20*) echo "comment posted to $ISSUE (nothing was overwritten)" ;;
    *) echo "Jira comment POST failed with HTTP $http" >&2; sed -n '1,5p' "$WORK/resp.json" >&2; exit 5 ;;
  esac
  exit 0
fi

# --- description: read before write ------------------------------------------
http=$(api -o "$WORK/issue.json" -w '%{http_code}' \
  "https://$HOST/rest/api/2/issue/$ISSUE?fields=description") || http="000"
case "$http" in
  200) ;;
  *) echo "could not read the current description of $ISSUE (HTTP $http)" >&2; exit 5 ;;
esac
jq -r '.fields.description // ""' "$WORK/issue.json" > "$WORK/existing.txt"

existing_bytes=$(wc -c < "$WORK/existing.txt" | tr -d ' ')
existing_lines=$(grep -c . "$WORK/existing.txt" || true)

mkdir -p "$BACKUP_DIR"
STAMP=$(date +%Y%m%dT%H%M%S)
BACKUP="$BACKUP_DIR/$ISSUE-description-$STAMP.txt"
cp "$WORK/existing.txt" "$BACKUP"
echo "existing description backed up: $BACKUP ($existing_lines non-empty line(s))"

if [ "$existing_bytes" -le 1 ]; then
  cp "$WORK/new.txt" "$WORK/final.txt"
  RESULT="written into an empty description"
elif [ "$MODE" = "append" ]; then
  { cat "$WORK/existing.txt"; printf '\n\n----\n\n'; cat "$WORK/new.txt"; } > "$WORK/final.txt"
  RESULT="appended below the existing description"
else
  if [ "$CONFIRM" != "1" ]; then
    echo "REFUSED: --mode replace would discard $existing_lines non-empty line(s) already in the description of $ISSUE." >&2
    echo "The current text is saved at $BACKUP. Re-run with --mode append to keep it, or add --confirm-overwrite to replace it deliberately." >&2
    exit 3
  fi
  cp "$WORK/new.txt" "$WORK/final.txt"
  RESULT="replaced the existing description (backup above)"
fi

if [ "$DRY_RUN" = "1" ]; then
  echo "dry-run: would PUT the description of $ISSUE - $RESULT"
  cat "$WORK/final.txt"
  exit 0
fi

jq -n --rawfile body "$WORK/final.txt" '{fields: {description: $body}}' > "$WORK/payload.json"
http=$(api -o "$WORK/resp.json" -w '%{http_code}' -X PUT \
  --data-binary @"$WORK/payload.json" \
  "https://$HOST/rest/api/2/issue/$ISSUE") || http="000"
case "$http" in
  20*) echo "description of $ISSUE updated - $RESULT" ;;
  *) echo "Jira description PUT failed with HTTP $http" >&2; sed -n '1,5p' "$WORK/resp.json" >&2; exit 5 ;;
esac
