#!/usr/bin/env bash
# cf — bash CLI for clawdforge
#
# Usage:
#   cf healthz
#   cf run "<prompt>"  [--model sonnet] [--system "..."] [--timeout 60] [--files token1,token2]
#   cf run -                                # read prompt from stdin (any size)
#   cf upload <path>   [--ttl 3600]
#   cf admin token-mint <name>  [--ip-cidrs cidr1,cidr2]
#   cf admin token-list
#   cf admin token-revoke <name>
#
# Configuration (env or ~/.config/clawdforge/cf.env, env wins):
#   CLAWDFORGE_URL           — default http://192.168.0.5:8800
#   CLAWDFORGE_TOKEN         — required for /run + /files
#   CLAWDFORGE_ADMIN_TOKEN   — required for admin/* subcommands
#
# Output:
#   - successful command → response JSON to stdout (pipe to jq freely)
#   - errors             → message to stderr, non-zero exit code
#
# Exit codes:
#   0  ok
#   1  generic error / curl failure
#   2  bad usage
#   3  missing token
#   4  HTTP 4xx (auth / not-found / bad-request)
#   5  HTTP 5xx (server / claude failure)

set -euo pipefail

# ---------- config loading -------------------------------------------------

_CFG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/clawdforge/cf.env"
if [[ -f "$_CFG_FILE" ]]; then
  # shellcheck disable=SC1090
  set -a; . "$_CFG_FILE"; set +a
fi

CLAWDFORGE_URL="${CLAWDFORGE_URL:-http://192.168.0.5:8800}"
CLAWDFORGE_URL="${CLAWDFORGE_URL%/}"

# ---------- helpers --------------------------------------------------------

_die() { echo "cf: $*" >&2; exit "${2:-1}"; }

_need_token() {
  [[ -n "${CLAWDFORGE_TOKEN:-}" ]] || _die "CLAWDFORGE_TOKEN not set (env or ~/.config/clawdforge/cf.env)" 3
}

_need_admin_token() {
  [[ -n "${CLAWDFORGE_ADMIN_TOKEN:-}" ]] || _die "CLAWDFORGE_ADMIN_TOKEN not set" 3
}

# Fire a curl request, capture body + status, exit on HTTP error.
# Args: METHOD URL TOKEN [DATA_OR_- [CONTENT_TYPE]]
_request() {
  local method="$1" url="$2" token="$3" data="${4:-}" ctype="${5:-application/json}"
  local body status
  local tmp=""
  tmp="$(mktemp)"
  trap '[[ -n "${tmp:-}" ]] && rm -f "$tmp"' RETURN

  local -a curl_args=(
    -sS -X "$method" "$url"
    -H "Authorization: Bearer $token"
    -H "Accept: application/json"
    -w '\n__cf_status__=%{http_code}'
    --max-time 600
  )

  if [[ -n "$data" ]]; then
    if [[ "$data" == "-" ]]; then
      cat > "$tmp"
      curl_args+=(-H "Content-Type: $ctype" --data-binary "@$tmp")
    else
      curl_args+=(-H "Content-Type: $ctype" --data-binary "$data")
    fi
  fi

  local resp
  resp="$(curl "${curl_args[@]}" 2>&1)" || _die "curl failed: $resp" 1

  status="${resp##*__cf_status__=}"
  body="${resp%__cf_status__=*}"
  body="${body%$'\n'}"   # trim trailing newline before status marker

  if [[ "$status" -ge 200 && "$status" -lt 300 ]]; then
    printf '%s\n' "$body"
    return 0
  fi
  echo "cf: HTTP $status" >&2
  printf '%s\n' "$body" >&2
  if [[ "$status" -ge 400 && "$status" -lt 500 ]]; then exit 4; fi
  exit 5
}

# Build a JSON object from --key value flags (each value is JSON-quoted as string).
# Numbers passed as $key=number are kept numeric. Booleans true/false kept bool.
# Lists are NOT supported — cmd_run handles its files-array specially.
_json_obj_from_assoc() {
  local first=1 out='{'
  for kv in "$@"; do
    local k="${kv%%=*}" v="${kv#*=}"
    if [[ "$first" == 0 ]]; then out+=','; fi
    first=0
    out+="\"$k\":"
    if [[ "$v" =~ ^-?[0-9]+(\.[0-9]+)?$ ]]; then
      out+="$v"
    elif [[ "$v" == "true" || "$v" == "false" ]]; then
      out+="$v"
    else
      # JSON-escape: backslash, quote, newline, tab, CR
      local esc="${v//\\/\\\\}"
      esc="${esc//\"/\\\"}"
      esc="${esc//$'\n'/\\n}"
      esc="${esc//$'\r'/\\r}"
      esc="${esc//$'\t'/\\t}"
      out+="\"$esc\""
    fi
  done
  out+='}'
  printf '%s\n' "$out"
}

# Print the error body's "error" field if it's JSON, else the whole body.
_extract_error() {
  local body="$1"
  if command -v jq >/dev/null 2>&1; then
    jq -r '.error // .detail // .' <<<"$body" 2>/dev/null || printf '%s' "$body"
  else
    printf '%s' "$body"
  fi
}

# ---------- subcommands ----------------------------------------------------

cmd_healthz() {
  _request GET "$CLAWDFORGE_URL/healthz" "${CLAWDFORGE_TOKEN:-${CLAWDFORGE_ADMIN_TOKEN:-}}"
}

cmd_run() {
  _need_token
  local prompt="" model="" system="" timeout_secs="" files=""
  if [[ $# -lt 1 ]]; then _die "usage: cf run <prompt|->  [--model] [--system] [--timeout] [--files]" 2; fi
  prompt="$1"; shift
  if [[ "$prompt" == "-" ]]; then prompt="$(cat)"; fi

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --model)    model="$2"; shift 2;;
      --system)   system="$2"; shift 2;;
      --timeout)  timeout_secs="$2"; shift 2;;
      --files)    files="$2"; shift 2;;
      *) _die "unknown flag: $1" 2;;
    esac
  done

  # Build JSON safely without depending on jq for input
  local args=("prompt=$prompt")
  [[ -n "$model" ]]        && args+=("model=$model")
  [[ -n "$system" ]]       && args+=("system=$system")
  [[ -n "$timeout_secs" ]] && args+=("timeout_secs=$timeout_secs")

  if [[ -n "$files" ]]; then
    # Build files array — keep this branch pure-bash to avoid jq dep
    local farr='['
    local first=1 IFS=,
    for ft in $files; do
      [[ "$first" == 0 ]] && farr+=','
      farr+="\"$ft\""
      first=0
    done
    farr+=']'
    # Compose final body manually since args helper doesn't do arrays
    local body
    body="$(_json_obj_from_assoc "${args[@]}")"
    body="${body%\}},\"files\":$farr}"
    body="${body//\}\}/\}}"   # tidy any double-close edge case
    # Guard: if substitution didn't apply correctly, fall back to manual splice
    if [[ "$body" != *'"files":'* ]]; then
      body="${body%\}},\"files\":$farr}"
    fi
    _request POST "$CLAWDFORGE_URL/run" "$CLAWDFORGE_TOKEN" "$body"
  else
    local body; body="$(_json_obj_from_assoc "${args[@]}")"
    _request POST "$CLAWDFORGE_URL/run" "$CLAWDFORGE_TOKEN" "$body"
  fi
}

cmd_upload() {
  _need_token
  local path="${1:-}" ttl=""
  [[ -n "$path" && -f "$path" ]] || _die "usage: cf upload <path> [--ttl 3600]" 2
  shift
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --ttl) ttl="$2"; shift 2;;
      *) _die "unknown flag: $1" 2;;
    esac
  done
  local -a curl_args=(
    -sS -X POST "$CLAWDFORGE_URL/files"
    -H "Authorization: Bearer $CLAWDFORGE_TOKEN"
    -H "Accept: application/json"
    -w '\n__cf_status__=%{http_code}'
    -F "file=@$path"
  )
  [[ -n "$ttl" ]] && curl_args+=(-F "ttl_secs=$ttl")
  local resp; resp="$(curl "${curl_args[@]}")" || _die "curl failed" 1
  local status="${resp##*__cf_status__=}"
  local body="${resp%__cf_status__=*}"; body="${body%$'\n'}"
  if [[ "$status" -ge 200 && "$status" -lt 300 ]]; then
    printf '%s\n' "$body"
  else
    echo "cf: HTTP $status" >&2; printf '%s\n' "$body" >&2
    [[ "$status" -lt 500 ]] && exit 4 || exit 5
  fi
}

cmd_admin() {
  local sub="${1:-}"; shift || true
  _need_admin_token
  case "$sub" in
    token-mint)
      local name="${1:-}"; shift || true
      [[ -n "$name" ]] || _die "usage: cf admin token-mint <name> [--ip-cidrs cidr1,cidr2]" 2
      local cidrs=""
      while [[ $# -gt 0 ]]; do
        case "$1" in
          --ip-cidrs) cidrs="$2"; shift 2;;
          *) _die "unknown flag: $1" 2;;
        esac
      done
      local body
      if [[ -n "$cidrs" ]]; then
        local arr='[' first=1 IFS=,
        for c in $cidrs; do
          [[ "$first" == 0 ]] && arr+=','; first=0
          arr+="\"$c\""
        done; arr+=']'
        body="{\"name\":\"$name\",\"ip_cidrs\":$arr}"
      else
        body="{\"name\":\"$name\",\"ip_cidrs\":[]}"
      fi
      _request POST "$CLAWDFORGE_URL/admin/tokens" "$CLAWDFORGE_ADMIN_TOKEN" "$body"
      ;;
    token-list)
      _request GET "$CLAWDFORGE_URL/admin/tokens" "$CLAWDFORGE_ADMIN_TOKEN"
      ;;
    token-revoke)
      local name="${1:-}"
      [[ -n "$name" ]] || _die "usage: cf admin token-revoke <name>" 2
      _request DELETE "$CLAWDFORGE_URL/admin/tokens/$name" "$CLAWDFORGE_ADMIN_TOKEN"
      ;;
    *) _die "unknown admin subcommand: $sub (token-mint|token-list|token-revoke)" 2;;
  esac
}

cmd_help() {
  sed -n '2,/^set -euo/{/^set -euo/q;p}' "${BASH_SOURCE[0]}" | sed 's/^# \?//'
}

# ---------- dispatch -------------------------------------------------------

cmd="${1:-help}"
case "$cmd" in
  -h|--help|help)  cmd_help;;
  healthz)         shift; cmd_healthz "$@";;
  run)             shift; cmd_run "$@";;
  upload)          shift; cmd_upload "$@";;
  admin)           shift; cmd_admin "$@";;
  *) _die "unknown command: $cmd (healthz|run|upload|admin|help)" 2;;
esac
