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

authentik_url="${AUTHENTIK_URL:-https://auth.burrow.net}"
bootstrap_token="${AUTHENTIK_BOOTSTRAP_TOKEN:-}"
application_slug="${AUTHENTIK_GOOGLE_WIF_APPLICATION_SLUG:-google-cloud}"
application_name="${AUTHENTIK_GOOGLE_WIF_APPLICATION_NAME:-Google Cloud WIF}"
provider_name="${AUTHENTIK_GOOGLE_WIF_PROVIDER_NAME:-Google Cloud WIF}"
template_slug="${AUTHENTIK_GOOGLE_WIF_TEMPLATE_SLUG:-ts}"
client_id="${AUTHENTIK_GOOGLE_WIF_CLIENT_ID:-google-wif.burrow.net}"
client_secret="${AUTHENTIK_GOOGLE_WIF_CLIENT_SECRET:-}"
launch_url="${AUTHENTIK_GOOGLE_WIF_LAUNCH_URL:-https://console.cloud.google.com/}"
redirect_uris_json="${AUTHENTIK_GOOGLE_WIF_REDIRECT_URIS_JSON:-[]}"
access_group="${AUTHENTIK_GOOGLE_WIF_ACCESS_GROUP:-}"

usage() {
  cat <<'EOF'
Usage: Scripts/authentik-sync-google-wif-oidc.sh

Reconciles the Authentik OIDC provider used as the Google Workload Identity
Federation issuer for Burrow release runners.

Required environment:
  AUTHENTIK_BOOTSTRAP_TOKEN
  AUTHENTIK_GOOGLE_WIF_CLIENT_SECRET

Optional environment:
  AUTHENTIK_URL
  AUTHENTIK_GOOGLE_WIF_APPLICATION_SLUG
  AUTHENTIK_GOOGLE_WIF_APPLICATION_NAME
  AUTHENTIK_GOOGLE_WIF_PROVIDER_NAME
  AUTHENTIK_GOOGLE_WIF_TEMPLATE_SLUG
  AUTHENTIK_GOOGLE_WIF_CLIENT_ID
  AUTHENTIK_GOOGLE_WIF_LAUNCH_URL
  AUTHENTIK_GOOGLE_WIF_REDIRECT_URIS_JSON
  AUTHENTIK_GOOGLE_WIF_ACCESS_GROUP (optional; leave empty for client credentials)

AUTHENTIK_GOOGLE_WIF_CLIENT_SECRET is the Authentik client-credentials secret
used by Forgejo runners. For service-account authentication, store
base64("<service-account-username>:<app-password-token>").
EOF
}

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
  usage
  exit 0
fi

if [[ -z "$bootstrap_token" ]]; then
  echo "error: AUTHENTIK_BOOTSTRAP_TOKEN is required" >&2
  exit 1
fi

if [[ -z "$client_secret" || "$client_secret" == PENDING* ]]; then
  echo "Google WIF OIDC client secret is not configured; skipping Authentik Google WIF sync." >&2
  exit 0
fi

if ! printf '%s' "$redirect_uris_json" | jq -e 'type == "array"' >/dev/null; then
  echo "error: AUTHENTIK_GOOGLE_WIF_REDIRECT_URIS_JSON must be a JSON array" >&2
  exit 1
fi

api() {
  local method="$1"
  local path="$2"
  local data="${3:-}"

  if [[ -n "$data" ]]; then
    curl -fsS \
      -X "$method" \
      -H "Authorization: Bearer ${bootstrap_token}" \
      -H "Content-Type: application/json" \
      -d "$data" \
      "${authentik_url}${path}"
  else
    curl -fsS \
      -X "$method" \
      -H "Authorization: Bearer ${bootstrap_token}" \
      "${authentik_url}${path}"
  fi
}

api_with_status() {
  local method="$1"
  local path="$2"
  local data="${3:-}"
  local response_file status

  response_file="$(mktemp)"
  trap 'rm -f "$response_file"' RETURN

  if [[ -n "$data" ]]; then
    status="$(
      curl -sS \
        -o "$response_file" \
        -w '%{http_code}' \
        -X "$method" \
        -H "Authorization: Bearer ${bootstrap_token}" \
        -H "Content-Type: application/json" \
        -d "$data" \
        "${authentik_url}${path}"
    )"
  else
    status="$(
      curl -sS \
        -o "$response_file" \
        -w '%{http_code}' \
        -X "$method" \
        -H "Authorization: Bearer ${bootstrap_token}" \
        "${authentik_url}${path}"
    )"
  fi

  printf '%s\n' "$status"
  cat "$response_file"
}

wait_for_authentik() {
  for _ in $(seq 1 90); do
    if curl -fsS "${authentik_url}/-/health/ready/" >/dev/null 2>&1; then
      return 0
    fi
    sleep 2
  done
  echo "error: Authentik did not become ready at ${authentik_url}" >&2
  exit 1
}

wait_for_authentik

template_provider="$(
  api GET "/api/v3/providers/oauth2/?page_size=200" \
    | jq -c --arg slug "$template_slug" '.results[]? | select(.assigned_application_slug == $slug)' \
    | head -n1
)"

if [[ -z "$template_provider" ]]; then
  echo "error: could not resolve Authentik OAuth provider template ${template_slug}" >&2
  exit 1
fi

authorization_flow="$(printf '%s\n' "$template_provider" | jq -r '.authorization_flow')"
invalidation_flow="$(printf '%s\n' "$template_provider" | jq -r '.invalidation_flow')"
property_mappings="$(printf '%s\n' "$template_provider" | jq -c '.property_mappings')"
signing_key="$(printf '%s\n' "$template_provider" | jq -r '.signing_key')"

provider_payload="$(
  jq -n \
    --arg name "$provider_name" \
    --arg authorization_flow "$authorization_flow" \
    --arg invalidation_flow "$invalidation_flow" \
    --arg client_id "$client_id" \
    --arg client_secret "$client_secret" \
    --arg signing_key "$signing_key" \
    --argjson property_mappings "$property_mappings" \
    --argjson redirect_uris "$redirect_uris_json" \
    '{
      name: $name,
      authorization_flow: $authorization_flow,
      invalidation_flow: $invalidation_flow,
      client_type: "confidential",
      grant_types: ["client_credentials"],
      client_id: $client_id,
      client_secret: $client_secret,
      include_claims_in_id_token: true,
      redirect_uris: ($redirect_uris | map({matching_mode: "strict", url: .})),
      property_mappings: $property_mappings,
      signing_key: $signing_key,
      issuer_mode: "per_provider",
      sub_mode: "hashed_user_id"
    }'
)"

existing_provider="$(
  api GET "/api/v3/providers/oauth2/?page_size=200" \
    | jq -c \
      --arg application_slug "$application_slug" \
      --arg provider_name "$provider_name" \
      '.results[]? | select(.assigned_application_slug == $application_slug or .name == $provider_name)' \
    | head -n1
)"

if [[ -n "$existing_provider" ]]; then
  provider_pk="$(printf '%s\n' "$existing_provider" | jq -r '.pk')"
  api PATCH "/api/v3/providers/oauth2/${provider_pk}/" "$provider_payload" >/dev/null
else
  provider_pk="$(
    api POST "/api/v3/providers/oauth2/" "$provider_payload" \
      | jq -r '.pk // empty'
  )"
fi

if [[ -z "${provider_pk:-}" ]]; then
  echo "error: Google WIF OIDC provider did not return a primary key" >&2
  exit 1
fi

application_payload="$(
  jq -n \
    --arg name "$application_name" \
    --arg slug "$application_slug" \
    --arg provider "$provider_pk" \
    --arg launch_url "$launch_url" \
    '{
      name: $name,
      slug: $slug,
      provider: ($provider | tonumber),
      meta_launch_url: $launch_url,
      open_in_new_tab: false,
      policy_engine_mode: "any"
    }'
)"

existing_application="$(
  api GET "/api/v3/core/applications/?page_size=200" \
    | jq -c --arg slug "$application_slug" '.results[]? | select(.slug == $slug)' \
    | head -n1
)"

if [[ -z "$existing_application" ]]; then
  direct_application_result="$(api_with_status GET "/api/v3/core/applications/${application_slug}/")"
  direct_application_status="$(printf '%s\n' "$direct_application_result" | sed -n '1p')"
  direct_application_body="$(printf '%s\n' "$direct_application_result" | sed '1d')"
  if [[ "$direct_application_status" == "200" ]]; then
    existing_application="$direct_application_body"
  fi
fi

if [[ -n "$existing_application" ]]; then
  application_pk="$(printf '%s\n' "$existing_application" | jq -r '.pk')"
  api PATCH "/api/v3/core/applications/${application_slug}/" "$application_payload" >/dev/null
else
  create_application_result="$(
    api_with_status POST "/api/v3/core/applications/" "$application_payload"
  )"
  create_application_status="$(printf '%s\n' "$create_application_result" | sed -n '1p')"
  create_application_body="$(printf '%s\n' "$create_application_result" | sed '1d')"

  if [[ "$create_application_status" =~ ^20[01]$ ]]; then
    application_pk="$(printf '%s\n' "$create_application_body" | jq -r '.pk // empty')"
  elif [[ "$create_application_status" == "400" ]] && printf '%s\n' "$create_application_body" | jq -e '
      (.slug // [] | index("Application with this slug already exists.")) != null
      or (.provider // [] | index("Application with this provider already exists.")) != null
    ' >/dev/null; then
    application_pk="$(
      api GET "/api/v3/core/applications/?page_size=200" \
        | jq -r --arg slug "$application_slug" '.results[]? | select(.slug == $slug) | .pk // empty' \
        | head -n1
    )"
    if [[ -z "$application_pk" ]]; then
      direct_application_result="$(api_with_status GET "/api/v3/core/applications/${application_slug}/")"
      direct_application_status="$(printf '%s\n' "$direct_application_result" | sed -n '1p')"
      direct_application_body="$(printf '%s\n' "$direct_application_result" | sed '1d')"
      if [[ "$direct_application_status" == "200" ]]; then
        application_pk="$(printf '%s\n' "$direct_application_body" | jq -r '.pk // empty')"
      fi
    fi
  else
    printf '%s\n' "$create_application_body" >&2
    echo "error: could not reconcile Authentik application ${application_slug}" >&2
    exit 1
  fi
fi

if [[ -z "${application_pk:-}" ]]; then
  echo "error: Google WIF OIDC application did not return a primary key" >&2
  exit 1
fi

delete_binding() {
  local binding_pk="$1"
  api DELETE "/api/v3/policies/bindings/${binding_pk}/" >/dev/null || true
}

delete_application_group_bindings_except() {
  local keep_group_pk="${1:-}"
  local binding_pks

  binding_pks="$(
    api GET "/api/v3/policies/bindings/?page_size=500" \
      | jq -r \
        --arg target "$application_pk" \
        --arg keep_group "$keep_group_pk" \
        '.results[]?
          | select((.target | tostring) == $target and .group != null)
          | select(($keep_group == "") or ((.group | tostring) != $keep_group))
          | .pk'
  )"

  while IFS= read -r binding_pk; do
    [[ -n "$binding_pk" ]] || continue
    delete_binding "$binding_pk"
  done <<< "$binding_pks"
}

if [[ -n "$access_group" ]]; then
  group_pk="$(
    api GET "/api/v3/core/groups/?page_size=200" \
      | jq -r --arg name "$access_group" '.results[]? | select(.name == $name) | .pk' \
      | head -n1
  )"
  if [[ -z "$group_pk" ]]; then
    echo "warning: Authentik Google WIF access group ${access_group} was not found; application policy left unchanged." >&2
  else
    delete_application_group_bindings_except "$group_pk"
    existing_binding="$(
      api GET "/api/v3/policies/bindings/?page_size=500" \
        | jq -r \
          --arg target "$application_pk" \
          --arg group "$group_pk" \
          '.results[]?
            | select((.target | tostring) == $target and (.group | tostring) == $group)
            | .pk' \
        | head -n1
    )"
    if [[ -z "$existing_binding" ]]; then
      api POST "/api/v3/policies/bindings/" "$(
        jq -n \
          --arg target "$application_pk" \
          --arg group "$group_pk" \
          '{
            target: $target,
            group: $group,
            negate: false,
            order: 0,
            enabled: true,
            timeout: 30,
            failure_result: false
          }'
      )" >/dev/null || true
    fi
  fi
else
  delete_application_group_bindings_except ""
fi

for _ in $(seq 1 30); do
  if curl -fsS "${authentik_url}/application/o/${application_slug}/.well-known/openid-configuration" >/dev/null 2>&1; then
    echo "Synced Authentik Google WIF OIDC application ${application_slug} (${application_name})."
    exit 0
  fi
  sleep 2
done

echo "warning: Google WIF OIDC issuer document for ${application_slug} was not immediately readable; keeping reconciled config." >&2
echo "Synced Authentik Google WIF OIDC application ${application_slug} (${application_name})."
