#!/usr/bin/env bash
set -euo pipefail
source "$SHELLOPS_DIR/.envrc"

USAGE="$(cat <<EOF
Commits cargo-fuzz crash artifacts to the corpus and opens a Forgejo PR.

Usage: action rust submit-fuzz-crashes [-h,--help] [project-dir] [target] [base]

Arguments:
  project-dir    Directory containing the fuzz package. Defaults to engine/parser.
  target         Fuzz target name. Defaults to lexer.
  base           Pull request base branch. Defaults to main.

Environment:
  FORGE_ADMIN_TOKEN   Required. API token for branch push and PR creation.
  FUZZ_CRASH_PR_ASSIGNEE  Optional. Defaults to jerrod.
  FUZZ_CRASH_PR_LABELS    Optional comma-separated labels. Defaults to area/lexer,quality/test.

Flags:
  -h, --help     Show this help text.
EOF
)"

DEFAULT_FUZZ_CRASH_PR_ASSIGNEE="jerrod"
DEFAULT_FUZZ_CRASH_PR_LABELS="area/lexer,quality/test"

COPIED_CRASHES=()

function main() {
  local project_dir="engine/parser" target="lexer" base="main"

  parse_args project_dir target base "$@"

  local corpus_dir artifacts_dir
  corpus_dir="$project_dir/fuzz/corpus/$target"
  artifacts_dir="$project_dir/fuzz/artifacts/$target"

  copy_crashes_to_corpus "$artifacts_dir" "$corpus_dir"
  (( ${#COPIED_CRASHES[@]} == 0 )) && exit 0
  require_token

  local branch
  branch="$(fuzz_crash_branch "$target")"
  git switch -c "$branch"
  git add "${COPIED_CRASHES[@]}"

  if git diff --cached --quiet; then
    log "No new fuzz crash corpus entries to submit."
    exit 0
  fi

  cli/git-commit -m "test: add $target fuzz crash regression [skip ci]"
  git push -u origin "$branch"
  create_crash_pr "$target" "$branch" "$base" "$corpus_dir"
}

function parse_args() {
  local -n _project_dir=$1
  local -n _target=$2
  local -n _base=$3
  shift 3

  while [[ $# -gt 0 ]]; do
    case "$1" in
      -h|--help) log "$USAGE"; exit 0 ;;
      -*)
        error "Unknown option: $1"
        log "$USAGE"
        exit 1
        ;;
      *)
        _project_dir="$1"
        shift
        [[ $# -gt 0 ]] && _target="$1" && shift
        [[ $# -gt 0 ]] && _base="$1" && shift
        ;;
    esac
  done
}

function require_token() {
  if [[ -z "${FORGE_ADMIN_TOKEN:-}" ]]; then
    error "FORGE_ADMIN_TOKEN is not set"
    exit 1
  fi
}

function copy_crashes_to_corpus() {
  local artifacts_dir="$1" corpus_dir="$2"
  local artifacts=()

  shopt -s nullglob
  artifacts=("$artifacts_dir"/crash-*)
  shopt -u nullglob

  if (( ${#artifacts[@]} == 0 )); then
    log "No fuzz crash artifacts found."
    exit 0
  fi

  mkdir -p "$corpus_dir"

  local artifact
  for artifact in "${artifacts[@]}"; do
    copy_crash_to_corpus "$artifact" "$corpus_dir"
  done
}

function copy_crash_to_corpus() {
  local artifact="$1" corpus_dir="$2"
  local hash destination

  hash="$(sha256sum "$artifact" | awk '{print $1}')"
  destination="$corpus_dir/$hash"

  if [[ -f "$destination" ]]; then
    log "Crash already exists in corpus: $hash"
    return
  fi

  cp "$artifact" "$destination"
  COPIED_CRASHES+=("$destination")
  log "Added fuzz crash corpus entry: $hash"
}

function fuzz_crash_branch() {
  local target="$1"
  local run_id="${GITHUB_RUN_ID:-manual}"
  local attempt="${GITHUB_RUN_ATTEMPT:-1}"

  echo "automation/fuzz-$target-$run_id-$attempt"
}

function create_crash_pr() {
  local target="$1" branch="$2" base="$3" corpus_dir="$4"
  local title body assignees labels options result url

  import "$SHELLOPS_DIR/lib/forgejo.api.sh"

  title="test: add $target fuzz crash regression"
  body="$(crash_pr_body "$target" "$corpus_dir")"
  assignees="$(fuzz_crash_pr_assignees)"
  labels="$(fuzz_crash_pr_labels "$FORGE_ADMIN_TOKEN")"

  options="$(jq_set "$(new_pull_request_options)" \
    '.title' "$title" \
    '.head' "$branch" \
    '.base' "$base" \
    '.body' "$body" \
    '.assignees' "$assignees" \
    '.labels' "$labels" \
    '.token' "$FORGE_ADMIN_TOKEN")"

  result="$(forgejo_create_pull_request "$options")"
  url="$(jq_get "$result" '.html_url' '.url' '')"
  log "Opened fuzz crash PR: $url"
}

function fuzz_crash_pr_assignees() {
  local assignee="${FUZZ_CRASH_PR_ASSIGNEE:-$DEFAULT_FUZZ_CRASH_PR_ASSIGNEE}"

  if [[ -z "$assignee" ]]; then
    echo "[]"
    return
  fi

  jq -cn --arg assignee "$assignee" '[$assignee]'
}

function fuzz_crash_pr_labels() {
  local token="$1"
  local label_names="${FUZZ_CRASH_PR_LABELS:-$DEFAULT_FUZZ_CRASH_PR_LABELS}"

  if [[ -z "$label_names" ]]; then
    echo "[]"
    return
  fi

  local label_options labels ids
  label_options="$(jq_set "$(new_label_options)" '.token' "$token")"
  labels="$(forgejo_list_labels "$label_options")"
  ids="[]"

  local label_name label_id
  local -a label_name_parts=()
  IFS=',' read -r -a label_name_parts <<< "$label_names"
  for label_name in "${label_name_parts[@]}"; do
    label_name="$(trim_label_name "$label_name")"
    [[ -z "$label_name" ]] && continue

    label_id="$(label_id_by_name "$labels" "$label_name")"
    if [[ -z "$label_id" ]]; then
      warn "Skipping unknown Forgejo label: $label_name"
      continue
    fi

    ids="$(jq -c --argjson id "$label_id" '. + [$id]' <<< "$ids")"
  done

  echo "$ids"
}

function trim_label_name() {
  local label_name="$1"
  label_name="${label_name#"${label_name%%[![:space:]]*}"}"
  label_name="${label_name%"${label_name##*[![:space:]]}"}"
  echo "$label_name"
}

function label_id_by_name() {
  local labels="$1" label_name="$2"
  jq -r --arg name "$label_name" '[.[] | select(.name == $name) | .id][0] // empty' <<< "$labels"
}

function crash_pr_body() {
  local target="$1" corpus_dir="$2"
  local run_url
  run_url="$(workflow_run_url)"

  printf '%s\n\n' "\`cargo-fuzz\` found a crash for the \`$target\` target."
  printf '%s\n\n' "This PR commits the crash artifact into \`$corpus_dir\` so fuzz replay catches the regression."
  [[ -n "$run_url" ]] && printf '%s\n' "Workflow run: $run_url"
}

function workflow_run_url() {
  if [[ -z "${GITHUB_SERVER_URL:-}" || -z "${GITHUB_REPOSITORY:-}" || -z "${GITHUB_RUN_ID:-}" ]]; then
    echo ""
    return
  fi

  echo "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  main "$@"
fi
