#!/usr/bin/env bash

:<<'DOC'
GitHub API client for releases.
Mirrors the interface of forgejo.api.sh but targets api.github.com.
DOC

import "$SHELLOPS_DIR/lib/manifest.api.sh"
import "$SHELLOPS_DIR/lib/http.api.sh"

__github_api__BASE="https://api.github.com"
__github_api__UPLOAD_BASE="https://uploads.github.com"
__github_api__RELEASE_ENDPOINT="/repos/:owner:/:repo:/releases"
__github_api__UPLOAD_ENDPOINT="/repos/:owner:/:repo:/releases/:release_id:/assets?name=:filename:"
__github_api__DISPATCH_ENDPOINT="/repos/:owner:/:repo:/actions/workflows/:workflow:/dispatches"
__github_api__WORKFLOW_RUNS_ENDPOINT="/repos/:owner:/:repo:/actions/workflows/:workflow:/runs"
__github_api__RUN_ENDPOINT="/repos/:owner:/:repo:/actions/runs/:run_id:"
__github_api__TAG_REF_ENDPOINT="/repos/:owner:/:repo:/git/ref/tags/:tag:"

function __github_api__validate_token() {
  local options="$1"
  local token

  token="$(jq_get "$options" '.token')"
  if [[ -z "$token" ]]; then
    error "No authorization token set"
    return 1
  fi

  echo "$token"
}

function __github_api__http_opts() {
  jq_set '{}' '.auth' "Bearer $1" '.accept' "application/vnd.github+json"
}

function new_github_release_options() {
  jq_set "{}" \
    '.owner'      "$(manifest_get org)" \
    '.repo'       "$(manifest_get repo)" \
    '.tag_name'   "" \
    '.name'       "" \
    '.body'       "" \
    '.prerelease' false \
    '.token'      ""
}

function new_github_upload_options() {
  jq_set "{}" \
    '.owner'        "$(manifest_get org)" \
    '.repo'         "$(manifest_get repo)" \
    '.release_id'   "" \
    '.filename'     "" \
    '.filepath'     "" \
    '.token'        ""
}

function __github_api__release_endpoint() {
  local options="$1"
  local owner repo endpoint

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"

  endpoint="${__github_api__BASE}${__github_api__RELEASE_ENDPOINT}"
  endpoint="${endpoint//:owner:/$owner}"
  endpoint="${endpoint//:repo:/$repo}"
  echo "$endpoint"
}

function __github_api__upload_endpoint() {
  local options="$1"
  local owner repo release_id filename endpoint

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"
  release_id="$(jq_get "$options" '.release_id')"
  filename="$(jq_get "$options" '.filename')"

  endpoint="${__github_api__UPLOAD_BASE}${__github_api__UPLOAD_ENDPOINT}"
  endpoint="${endpoint//:owner:/$owner}"
  endpoint="${endpoint//:repo:/$repo}"
  endpoint="${endpoint//:release_id:/$release_id}"
  endpoint="${endpoint//:filename:/$filename}"
  echo "$endpoint"
}

function __github_api__release_body() {
  local options="$1"
  jq_dump "$options" "{tag_name,name,body,prerelease}"
}

:<<'DOC'
Creates a release via the GitHub API.

Usage: github_create_release "$release_options"
DOC
function github_create_release() {
  local options="$1"
  local token endpoint body http_opts

  token="$(__github_api__validate_token "$options")" || return 1
  http_opts="$(__github_api__http_opts "$token")"
  endpoint="$(__github_api__release_endpoint "$options")"
  body="$(__github_api__release_body "$options")"

  http_post "$endpoint" "$body" "$http_opts"
}

:<<'DOC'
Uploads a file as a release asset on GitHub.

Usage: github_upload_artifact "$upload_options"
DOC
function github_upload_artifact() {
  local options="$1"
  local filepath filename token endpoint http_opts

  filepath="$(jq_get "$options" '.filepath')"
  filename="$(jq_get "$options" '.filename')"
  token="$(__github_api__validate_token "$options")" || return 1

  if [[ -z "$filepath" || -z "$filename" ]]; then
    error "Incomplete file upload information. name: '$filename', path: '$filepath'"
    return 1
  fi

  endpoint="$(__github_api__upload_endpoint "$options")"
  http_opts="$(__github_api__http_opts "$token")"
  http_opts="$(jq_set "$http_opts" '.upload_mode' 'binary')"

  http_upload "$endpoint" "$filepath" "$http_opts"
}

# ==========================================================================
# Workflow Dispatch
# ==========================================================================

function new_github_dispatch_options() {
  jq_set "{}" \
    '.owner'    "$(manifest_get org)" \
    '.repo'     "$(manifest_get repo)" \
    '.workflow' "" \
    '.ref'      "" \
    '.token'    ""
}

function __github_api__dispatch_endpoint() {
  local options="$1"
  local owner repo workflow endpoint

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"
  workflow="$(jq_get "$options" '.workflow')"

  endpoint="${__github_api__BASE}${__github_api__DISPATCH_ENDPOINT}"
  endpoint="${endpoint//:owner:/$owner}"
  endpoint="${endpoint//:repo:/$repo}"
  endpoint="${endpoint//:workflow:/$workflow}"
  echo "$endpoint"
}

:<<'DOC'
Dispatches a GitHub Actions workflow on a given ref.

Usage: github_dispatch_workflow "$dispatch_options"
DOC
function github_dispatch_workflow() {
  local options="$1"
  local token endpoint ref body http_opts

  token="$(__github_api__validate_token "$options")" || return 1
  http_opts="$(__github_api__http_opts "$token")"
  ref="$(jq_get "$options" '.ref')"
  endpoint="$(__github_api__dispatch_endpoint "$options")"
  body="$(jq_set '{}' '.ref' "$ref")"

  http_post "$endpoint" "$body" "$http_opts"
}

# ==========================================================================
# Workflow Runs
# ==========================================================================

function new_github_run_options() {
  jq_set "{}" \
    '.owner'  "$(manifest_get org)" \
    '.repo'   "$(manifest_get repo)" \
    '.run_id' "" \
    '.token'  ""
}

:<<'DOC'
Lists workflow runs, optionally filtered by branch/ref and event type.
Returns the raw JSON response from the GitHub API.

Usage: github_list_workflow_runs "$dispatch_options"
DOC
function github_list_workflow_runs() {
  local options="$1"
  local owner repo workflow token endpoint http_opts

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"
  workflow="$(jq_get "$options" '.workflow')"
  token="$(__github_api__validate_token "$options")" || return 1
  http_opts="$(__github_api__http_opts "$token")"

  endpoint="${__github_api__BASE}${__github_api__WORKFLOW_RUNS_ENDPOINT}"
  endpoint="${endpoint//:owner:/$owner}"
  endpoint="${endpoint//:repo:/$repo}"
  endpoint="${endpoint//:workflow:/$workflow}"

  local ref
  ref="$(jq_get "$options" '.ref')"
  if [[ -n "$ref" && "$ref" != "null" ]]; then
    endpoint="${endpoint}?branch=${ref}&per_page=10"
  else
    endpoint="${endpoint}?per_page=10"
  fi

  http_get "$endpoint" "$http_opts"
}

:<<'DOC'
Waits for a dispatched workflow to be picked up as a run.
Returns the run JSON once it appears, or returns 1 on timeout.

Expects dispatch_options with owner, repo, workflow, ref, token.
Optional second argument: max attempts (default 10).
Optional third argument: interval in seconds (default 5).

Usage: github_wait_for_run_start "$dispatch_options" [attempts] [interval]
DOC
function github_wait_for_run_start() {
  local options="$1"
  local max_attempts="${2:-10}"
  local interval="${3:-5}"

  for _ in $(seq 1 "$max_attempts"); do
    local runs_json run_id
    runs_json="$(github_list_workflow_runs "$options")"
    run_id="$(jq_get "$runs_json" '.workflow_runs[0].id' "")"

    if [[ -n "$run_id" && "$run_id" != "null" ]]; then
      jq_get "$runs_json" '.workflow_runs[0]'
      return 0
    fi

    sleep "$interval"
  done

  return 1
}

:<<'DOC'
Gets a single workflow run by ID.

Usage: github_get_run "$run_options"
DOC
function github_get_run() {
  local options="$1"
  local owner repo run_id token endpoint http_opts

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"
  run_id="$(jq_get "$options" '.run_id')"
  token="$(__github_api__validate_token "$options")" || return 1
  http_opts="$(__github_api__http_opts "$token")"

  endpoint="${__github_api__BASE}${__github_api__RUN_ENDPOINT}"
  endpoint="${endpoint//:owner:/$owner}"
  endpoint="${endpoint//:repo:/$repo}"
  endpoint="${endpoint//:run_id:/$run_id}"

  http_get "$endpoint" "$http_opts"
}

:<<'DOC'
Re-runs a workflow run. Returns 0 on success, 1 on failure.

Usage: github_rerun_workflow "$run_options"
DOC
function github_rerun_workflow() {
  local options="$1"
  local owner repo run_id token endpoint http_opts

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"
  run_id="$(jq_get "$options" '.run_id')"
  token="$(__github_api__validate_token "$options")" || return 1
  http_opts="$(__github_api__http_opts "$token")"

  endpoint="${__github_api__BASE}${__github_api__RUN_ENDPOINT}"
  endpoint="${endpoint//:owner:/$owner}"
  endpoint="${endpoint//:repo:/$repo}"
  endpoint="${endpoint//:run_id:/$run_id}"

  http_post "${endpoint}/rerun" "" "$http_opts" > /dev/null
}

:<<'DOC'
Cancels a workflow run. Returns 0 on success, 1 on failure.

Usage: github_cancel_run "$run_options"
DOC
function github_cancel_run() {
  local options="$1"
  local owner repo run_id token endpoint http_opts

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"
  run_id="$(jq_get "$options" '.run_id')"
  token="$(__github_api__validate_token "$options")" || return 1
  http_opts="$(__github_api__http_opts "$token")"

  endpoint="${__github_api__BASE}${__github_api__RUN_ENDPOINT}"
  endpoint="${endpoint//:owner:/$owner}"
  endpoint="${endpoint//:repo:/$repo}"
  endpoint="${endpoint//:run_id:/$run_id}"

  http_post "${endpoint}/cancel" "" "$http_opts" > /dev/null
}

:<<'DOC'
Polls a workflow run until it reaches a terminal state (completed).
Returns the final run JSON.

Optional second argument: max attempts (default 60).
Optional third argument: interval in seconds (default 15).

Usage: github_wait_for_run_result "$run_options" [attempts] [interval]
DOC
function github_wait_for_run_result() {
  local options="$1"
  local max_attempts="${2:-60}"
  local interval="${3:-15}"

  for _ in $(seq 1 "$max_attempts"); do
    local run_json status
    run_json="$(github_get_run "$options")"
    status="$(jq_get "$run_json" '.status')"

    if [[ "$status" == "completed" ]]; then
      echo "$run_json"
      return 0
    fi

    sleep "$interval"
  done

  error "Timed out waiting for run $(jq_get "$options" '.run_id') to complete"
  return 1
}

# ==========================================================================
# Repository Queries
# ==========================================================================

function new_github_repo_options() {
  jq_set "{}" \
    '.owner' "$(manifest_get org)" \
    '.repo'  "$(manifest_get repo)" \
    '.tag'   "" \
    '.token' ""
}

function __github_api__tag_ref_endpoint() {
  local options="$1"
  local owner repo tag endpoint

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"
  tag="$(jq_get "$options" '.tag')"

  endpoint="${__github_api__BASE}${__github_api__TAG_REF_ENDPOINT}"
  endpoint="${endpoint//:owner:/$owner}"
  endpoint="${endpoint//:repo:/$repo}"
  endpoint="${endpoint//:tag:/$tag}"
  echo "$endpoint"
}

:<<'DOC'
Checks whether a tag ref exists on a GitHub repository.
Returns 0 if the tag exists, 1 otherwise.

Usage: github_tag_exists "$repo_options"
DOC
function github_tag_exists() {
  local options="$1"
  local token endpoint http_opts

  token="$(__github_api__validate_token "$options")" || return 1
  http_opts="$(__github_api__http_opts "$token")"
  endpoint="$(__github_api__tag_ref_endpoint "$options")"

  http_get "$endpoint" "$http_opts" > /dev/null 2>&1
}

:<<'DOC'
Lists all v* tags on a GitHub repository.

Usage: github_list_tags "$repo_options"
DOC
function github_list_tags() {
  local options="$1"
  local owner repo token http_opts

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"
  token="$(__github_api__validate_token "$options")" || return 1
  http_opts="$(__github_api__http_opts "$token")"

  http_get \
    "${__github_api__BASE}/repos/${owner}/${repo}/tags?per_page=100" \
    "$http_opts" \
    | jq -r '.[].name | select(startswith("v"))'
}

:<<'DOC'
Lists all release tag names on a GitHub repository.

Usage: github_list_release_tags "$repo_options"
DOC
function github_list_release_tags() {
  local options="$1"
  local owner repo token http_opts

  owner="$(jq_get "$options" '.owner')"
  repo="$(jq_get "$options" '.repo')"
  token="$(__github_api__validate_token "$options")" || return 1
  http_opts="$(__github_api__http_opts "$token")"

  http_get \
    "${__github_api__BASE}/repos/${owner}/${repo}/releases?per_page=100" \
    "$http_opts" \
    | jq -r '.[].tag_name'
}
