#!/usr/bin/env bash
# ps-encoded.sh — dispatch PowerShell through quote-mangling layers safely.
#
# THE point of this helper: a PS payload that traverses CommandLineToArgvW
# (curl.exe args, plink, ScreenConnect's command box, RMM->cmd.exe) gets its
# embedded double-quotes stripped and its UNC backslashes halved — the single
# most-repeated friction class in errorlog.md (ref=feedback_windows_quote_stripping,
# 9+ hits). -EncodedCommand (UTF-16LE base64) has NO quotes, backslashes, or
# dollar signs to mangle, so the script arrives byte-exact. Write the script
# to a FILE (or stdin), let this helper encode + deliver it.
#
# Usage:
#   ps-encoded.sh encode <script.ps1|->                 print the one-liner for
#                                                       ScreenConnect / plink / any paste
#   ps-encoded.sh rmm <agent-uuid> <script.ps1|->       dispatch via GuruRMM + poll
#       [--timeout <sec>]     agent-side timeout_seconds (default 120)
#       [--user-session]      context: user_session (WTS-impersonated desktop user)
#       [--no-wait]           dispatch only; print command id, skip polling
#       [--force]             override the size refusal (see below)
#
# Size guards (agent chokes on ~7KB command bodies; encoding inflates ~2.67x):
#   encoded > 4000 chars -> [WARNING]; encoded > 6000 chars -> refuse unless
#   --force (split the script, or stage it on the endpoint and run the file).
#
# Exit codes: 0 ok; 1 usage/encode error; 2 size refusal; 3 dispatch/poll failure.

set -u
ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
_logerr() { bash "$ROOT/.claude/scripts/log-skill-error.sh" "ps-encoded" "$@" >/dev/null 2>&1 || true; }

usage() { sed -n '2,26p' "$0"; exit 1; }

read_script() {  # $1 = file path or "-"
  if [ "$1" = "-" ]; then cat
  elif [ -f "$1" ]; then cat "$1"
  else echo "[ERROR] script file not found: $1" >&2; exit 1
  fi
}

encode_b64() {  # stdin: UTF-8 script -> stdout: UTF-16LE base64 (no BOM, no wraps)
  if command -v iconv >/dev/null 2>&1; then
    iconv -f UTF-8 -t UTF-16LE | base64 -w0
  else
    # Git-for-Windows ships the iconv *library* (msys-iconv-2.dll) but NOT iconv.exe,
    # and has no pacman to install it. Fall back to Python (present fleet-wide) for the
    # UTF-16LE -> base64 encode. Binary stdin/stdout so no CRLF translation.
    local py
    py="$(command -v py || command -v python3 || command -v python || true)"
    if [ -z "$py" ]; then
      echo "[ERROR] neither iconv nor python available for UTF-16LE encoding" >&2
      return 1
    fi
    "$py" -c "import sys,base64; sys.stdout.write(base64.b64encode(sys.stdin.buffer.read().decode('utf-8').encode('utf-16-le')).decode())"
  fi
}

size_gate() {  # $1 = encoded string, $2 = force flag (0/1)
  local n=${#1}
  if [ "$n" -gt 6000 ] && [ "${2:-0}" != "1" ]; then
    echo "[ERROR] encoded payload is ${n} chars (>6000); the agent fails on bodies this large." >&2
    echo "        Split the script into sections, or stage it as a file on the endpoint" >&2
    echo "        and dispatch 'powershell -File <path>'. Override with --force." >&2
    return 1
  elif [ "$n" -gt 4000 ]; then
    echo "[WARNING] encoded payload is ${n} chars — near the agent's body-size failure zone (~7KB)." >&2
  fi
  return 0
}

cmd_encode() {
  local src="${1:-}"; [ -z "$src" ] && usage
  local b64
  b64="$(read_script "$src" | encode_b64)"
  [ -z "$b64" ] && { echo "[ERROR] encoding produced nothing" >&2; _logerr "encode produced empty output" --context "src=$src"; exit 1; }
  size_gate "$b64" 1 || true   # encode mode: warn only, the paste target may cope
  printf 'powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand %s\n' "$b64"
}

cmd_rmm() {
  local agent="${1:-}"; shift || true
  local src="${1:-}"; shift || true
  [ -z "$agent" ] || [ -z "$src" ] && usage
  local timeout=120 context="" wait=1 force=0
  while [ $# -gt 0 ]; do
    case "$1" in
      --timeout) timeout="${2:?}"; shift 2 ;;
      --user-session) context="user_session"; shift ;;
      --no-wait) wait=0; shift ;;
      --force) force=1; shift ;;
      *) echo "[ERROR] unknown option: $1" >&2; usage ;;
    esac
  done

  local b64
  b64="$(read_script "$src" | encode_b64)"
  [ -z "$b64" ] && { echo "[ERROR] encoding produced nothing" >&2; _logerr "encode produced empty output" --context "src=$src"; exit 1; }
  size_gate "$b64" "$force" || exit 2

  eval "$(bash "$ROOT/.claude/scripts/rmm-auth.sh")"
  if [ -z "${TOKEN:-}" ] || [ -z "${RMM:-}" ]; then
    echo "[ERROR] RMM auth failed (no TOKEN/RMM)" >&2
    _logerr "RMM auth failed via rmm-auth.sh" --context "agent=$agent"
    exit 3
  fi

  # command_type=shell (cmd.exe): the base64 blob is a single quote-free token,
  # so no layer between here and powershell.exe can mangle it. timeout_seconds
  # is the field the agent honors — 'timeout' is silently ignored (errorlog).
  local oneliner payload resp cmd_id status
  oneliner="powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand ${b64}"
  payload="$(jq -nc --arg ct shell --arg cmd "$oneliner" --argjson to "$timeout" \
      --arg cx "$context" \
      '{command_type:$ct, command:$cmd, timeout_seconds:$to}
       + (if $cx != "" then {context:$cx} else {} end)')"
  resp="$(printf '%s' "$payload" | curl -s -m 20 -X POST "$RMM/api/agents/$agent/command" \
      -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
      --data-binary @-)"
  cmd_id="$(printf '%s' "$resp" | jq -r '.command_id // empty' 2>/dev/null)"
  status="$(printf '%s' "$resp" | jq -r '.status // empty' 2>/dev/null)"
  if [ -z "$cmd_id" ]; then
    echo "[ERROR] dispatch failed: $resp" >&2
    _logerr "EncodedCommand dispatch failed" --context "agent=$agent resp=${resp:0:80}"
    exit 3
  fi
  echo "[OK] dispatched command_id=$cmd_id (initial status: ${status:-unknown}, timeout_seconds=$timeout)"
  [ "$wait" = "0" ] && exit 0

  local max_polls=$(( (timeout + 30) / 5 )) count=0 result
  while [ $count -lt $max_polls ]; do
    result="$(curl -s -m 15 "$RMM/api/commands/$cmd_id" -H "Authorization: Bearer $TOKEN")"
    status="$(printf '%s' "$result" | jq -r '.status // empty' 2>/dev/null)"
    case "$status" in
      completed|failed|cancelled|interrupted)
        echo "--- status: $status ---"
        printf '%s' "$result" | jq -r '
          "exit_code: \(.exit_code // "n/a")",
          "--- stdout ---", (.stdout // .output // ""),
          "--- stderr ---", (.stderr // "")' 2>/dev/null || printf '%s\n' "$result"
        [ "$status" = "completed" ] && exit 0 || exit 3 ;;
      running|pending) count=$((count+1)); sleep 5 ;;
      *) echo "[ERROR] empty/unknown status — response: $result" >&2
         _logerr "poll returned empty status" --context "cmd=$cmd_id agent=$agent"
         exit 3 ;;
    esac
  done
  echo "[WARNING] poll timeout after $((max_polls*5))s — command $cmd_id may still be running (last: $status)"
  exit 3
}

case "${1:-}" in
  encode) shift; cmd_encode "$@" ;;
  rmm)    shift; cmd_rmm "$@" ;;
  *) usage ;;
esac
