#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<'EOF'
Usage: Scripts/forgejo-dispatch.sh <workflow> [--ref <ref>] [--inputs <json>] [--inputs-file <path>] [--return-run-info]

Dispatch a Burrow Forgejo workflow through the Forgejo API.

Environment:
  FORGEJO_BASE_URL                  Default: https://git.burrow.net
  FORGEJO_REPO                      Default: hackclub/burrow
  FORGEJO_PAT                       Token string
  FORGEJO_PAT_FILE                  Path to plaintext token
  FORGEJO_DISPATCH_HOST_FALLBACK    auto, always, or never. Default: auto
EOF
}

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
base_url="${FORGEJO_BASE_URL:-https://git.burrow.net}"
repo_slug="${FORGEJO_REPO:-hackclub/burrow}"
host_fallback="${FORGEJO_DISPATCH_HOST_FALLBACK:-auto}"
original_args=("$@")
workflow=""
ref="main"
inputs_json=""
return_run_info="false"

case "$host_fallback" in
  auto|always|never) ;;
  *)
    echo "FORGEJO_DISPATCH_HOST_FALLBACK must be auto, always, or never." >&2
    exit 64
    ;;
esac

host_dispatch() {
  local reason="$1"
  local fallback="${repo_root}/Scripts/forgejo-dispatch-via-host.sh"

  if [[ ! -x "$fallback" ]]; then
    return 1
  fi

  echo "Forgejo API dispatch ${reason}; retrying through the forge host." >&2
  exec "$fallback" "${original_args[@]}"
}

if [[ "$host_fallback" == "always" ]]; then
  if ! host_dispatch "forced by FORGEJO_DISPATCH_HOST_FALLBACK=always"; then
    echo "Forge host dispatcher is not available." >&2
    exit 1
  fi
fi

while [[ $# -gt 0 ]]; do
  case "$1" in
    -h|--help)
      usage
      exit 0
      ;;
    --ref)
      ref="${2:-}"
      shift 2
      ;;
    --inputs)
      inputs_json="${2:-}"
      shift 2
      ;;
    --inputs-file)
      inputs_json="$(cat "${2:-}")"
      shift 2
      ;;
    --return-run-info)
      return_run_info="true"
      shift
      ;;
    *)
      if [[ -z "$workflow" ]]; then
        workflow="$1"
        shift
      else
        echo "Unexpected argument: $1" >&2
        usage >&2
        exit 64
      fi
      ;;
  esac
done

if [[ -z "$workflow" ]]; then
  echo "Missing workflow name or file, for example build-release.yml." >&2
  usage >&2
  exit 64
fi

extract_dispatcher_token() {
  python3 - "$1" <<'PY'
import re
import sys
from pathlib import Path

path = Path(sys.argv[1])
in_forgejo = False
for line in path.read_text(encoding="utf-8").splitlines():
    if re.match(r"^[A-Za-z0-9_-]+:", line):
        in_forgejo = line.startswith("forgejo:")
        continue
    if not in_forgejo:
        continue
    match = re.match(r"\s+token:\s*['\"]?([^'\"]+)['\"]?\s*$", line)
    if match:
        print(match.group(1))
        break
PY
}

decrypt_age_secret() {
  local path="$1"

  if [[ ! -f "$path" ]]; then
    return 1
  fi

  if command -v nix >/dev/null 2>&1; then
    nix run "${repo_root}#agenix" -- -d "$path" 2>/dev/null && return 0
  fi

  if command -v agenix >/dev/null 2>&1; then
    agenix -d "$path" 2>/dev/null && return 0
  fi

  return 1
}

pat="${FORGEJO_PAT:-}"
pat_source="none"
if [[ -n "$pat" ]]; then
  pat_source="FORGEJO_PAT"
fi

if [[ -z "$pat" && -n "${FORGEJO_PAT_FILE:-}" && -f "$FORGEJO_PAT_FILE" ]]; then
  pat="$(tr -d '\r\n' < "$FORGEJO_PAT_FILE")"
  if [[ -n "$pat" ]]; then
    pat_source="FORGEJO_PAT_FILE"
  fi
fi

if [[ -z "$pat" ]]; then
  for default_file in \
    "${repo_root}/intake/forgejo_dispatch_pat.txt" \
    "${repo_root}/intake/forgejo_nsc_dispatcher.yaml"
  do
    if [[ -s "$default_file" ]]; then
      if [[ "$default_file" == *.yaml ]]; then
        pat="$(extract_dispatcher_token "$default_file" | tr -d '\r\n')"
      else
        pat="$(tr -d '\r\n' < "$default_file")"
      fi
    fi
    if [[ -n "$pat" ]]; then
      pat_source="$default_file"
      break
    fi
  done
fi

if [[ -z "$pat" ]]; then
  decrypted_config="$(mktemp)"
  trap 'rm -f "$decrypted_config"' EXIT
  if decrypt_age_secret "${repo_root}/secrets/infra/forgejo-nsc-dispatcher-config.age" > "$decrypted_config"; then
    pat="$(extract_dispatcher_token "$decrypted_config" | tr -d '\r\n')"
    if [[ -n "$pat" ]]; then
      pat_source="secrets/infra/forgejo-nsc-dispatcher-config.age"
    fi
  fi
fi

if [[ -z "$pat" ]]; then
  if [[ "$host_fallback" == "auto" ]]; then
    if host_dispatch "has no local token"; then
      exit 0
    fi
  fi
  echo "Forgejo token not found. Set FORGEJO_PAT or FORGEJO_PAT_FILE." >&2
  exit 1
fi

if [[ -n "$inputs_json" ]]; then
  if ! printf '%s\n' "$inputs_json" | jq -e 'type == "object"' >/dev/null 2>&1; then
    echo "--inputs must be a JSON object." >&2
    exit 64
  fi
  inputs_json="$(printf '%s\n' "$inputs_json" | jq -c 'with_entries(.value |= tostring)')"
  payload="$(jq -n --arg ref "$ref" --argjson inputs "$inputs_json" --argjson return_run_info "$return_run_info" \
    '{ref:$ref, inputs:$inputs} + (if $return_run_info then {return_run_info:true} else {} end)')"
else
  payload="$(jq -n --arg ref "$ref" --argjson return_run_info "$return_run_info" \
    '{ref:$ref} + (if $return_run_info then {return_run_info:true} else {} end)')"
fi

workflow_path="$(python3 - "$workflow" <<'PY'
import sys
import urllib.parse

print(urllib.parse.quote(sys.argv[1], safe=""))
PY
)"

url="${base_url%/}/api/v1/repos/${repo_slug}/actions/workflows/${workflow_path}/dispatches"
response_file="$(mktemp)"
trap 'rm -f "$response_file" "${decrypted_config:-}"' EXIT

curl_status=0
http_code="$(
  curl -sS -o "$response_file" -w '%{http_code}' -X POST \
    -H "Authorization: token ${pat}" \
    -H "Content-Type: application/json" \
    --data-binary "$payload" \
    "$url"
)" || curl_status="$?"

if [[ "$curl_status" != "0" || "$http_code" != 2* ]]; then
  if [[ "$host_fallback" == "auto" && "$pat_source" != "FORGEJO_PAT" && ( "$http_code" == "401" || "$http_code" == "403" ) ]]; then
    if host_dispatch "returned HTTP ${http_code} with ${pat_source}"; then
      exit 0
    fi
  fi

  cat "$response_file" >&2 || true
  if [[ "$curl_status" != "0" ]]; then
    exit "$curl_status"
  fi
  exit 22
fi

cat "$response_file"
if [[ "$return_run_info" != "true" ]]; then
  echo "Dispatched ${workflow} on ${ref}."
fi
