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

authentik_url="${AUTHENTIK_URL:-https://auth.burrow.net}"
bootstrap_token="${AUTHENTIK_BOOTSTRAP_TOKEN:-}"
provider_slug="${AUTHENTIK_TAILNET_PROVIDER_SLUG:-ts}"
provider_slugs_json="${AUTHENTIK_TAILNET_PROVIDER_SLUGS_JSON:-}"
authentication_flow_name="${AUTHENTIK_TAILNET_AUTHENTICATION_FLOW_NAME:-Burrow Tailnet Authentication}"
authentication_flow_slug="${AUTHENTIK_TAILNET_AUTHENTICATION_FLOW_SLUG:-burrow-tailnet-authentication}"
identification_stage_name="${AUTHENTIK_TAILNET_IDENTIFICATION_STAGE_NAME:-burrow-tailnet-identification-stage}"
password_stage_name="${AUTHENTIK_TAILNET_PASSWORD_STAGE_NAME:-burrow-tailnet-password-stage}"
user_login_stage_name="${AUTHENTIK_TAILNET_USER_LOGIN_STAGE_NAME:-burrow-tailnet-user-login-stage}"
google_source_slug="${AUTHENTIK_TAILNET_GOOGLE_SOURCE_SLUG:-google}"

usage() {
  cat <<'EOF'
Usage: Scripts/authentik-sync-tailnet-auth-flow.sh

Required environment:
  AUTHENTIK_BOOTSTRAP_TOKEN

Optional environment:
  AUTHENTIK_URL
  AUTHENTIK_TAILNET_PROVIDER_SLUG
  AUTHENTIK_TAILNET_PROVIDER_SLUGS_JSON
  AUTHENTIK_TAILNET_AUTHENTICATION_FLOW_NAME
  AUTHENTIK_TAILNET_AUTHENTICATION_FLOW_SLUG
  AUTHENTIK_TAILNET_IDENTIFICATION_STAGE_NAME
  AUTHENTIK_TAILNET_PASSWORD_STAGE_NAME
  AUTHENTIK_TAILNET_USER_LOGIN_STAGE_NAME
  AUTHENTIK_TAILNET_GOOGLE_SOURCE_SLUG
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 [[ -n "$provider_slugs_json" ]]; then
  if ! printf '%s' "$provider_slugs_json" | jq -e 'type == "array" and length > 0 and all(.[]; type == "string" and length > 0)' >/dev/null; then
    echo "error: AUTHENTIK_TAILNET_PROVIDER_SLUGS_JSON must be a non-empty JSON array of strings" >&2
    exit 1
  fi
else
  provider_slugs_json="$(jq -cn --arg slug "$provider_slug" '[$slug]')"
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
}

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
}

lookup_stage_by_name() {
  local path="$1"
  local name="$2"

  api GET "${path}?page_size=200" \
    | jq -c --arg name "$name" '.results[]? | select(.name == $name)' \
    | head -n1
}

lookup_flow_pk() {
  local slug="$1"

  api GET "/api/v3/flows/instances/?slug=${slug}" \
    | jq -r '.results[]? | select(.slug != null) | .pk // empty' \
    | head -n1
}

lookup_source_pk() {
  local slug="$1"

  api GET "/api/v3/sources/oauth/?page_size=200&slug=${slug}" \
    | jq -r --arg slug "$slug" '.results[]? | select(.slug == $slug) | .pk // empty' \
    | head -n1
}

ensure_password_stage() {
  local existing payload stage_pk

  existing="$(lookup_stage_by_name "/api/v3/stages/password/" "$password_stage_name")"
  payload="$(
    jq -cn \
      --arg name "$password_stage_name" \
      '{
        name: $name,
        backends: [
          "authentik.core.auth.InbuiltBackend",
          "authentik.core.auth.TokenBackend"
        ],
        allow_show_password: false,
        failed_attempts_before_cancel: 5
      }'
  )"

  if [[ -n "$existing" ]]; then
    stage_pk="$(printf '%s\n' "$existing" | jq -r '.pk')"
    api PATCH "/api/v3/stages/password/${stage_pk}/" "$payload" >/dev/null
  else
    stage_pk="$(
      api POST "/api/v3/stages/password/" "$payload" \
        | jq -r '.pk // empty'
    )"
  fi

  printf '%s\n' "$stage_pk"
}

ensure_identification_stage() {
  local password_stage_pk="$1"
  local google_source_pk="$2"
  local existing payload stage_pk sources_json

  existing="$(lookup_stage_by_name "/api/v3/stages/identification/" "$identification_stage_name")"
  if [[ -n "$google_source_pk" ]]; then
    sources_json="$(jq -cn --arg source "$google_source_pk" '[$source]')"
  else
    sources_json='[]'
  fi

  payload="$(
    jq -cn \
      --arg name "$identification_stage_name" \
      --arg password_stage "$password_stage_pk" \
      --argjson sources "$sources_json" \
      '{
        name: $name,
        user_fields: ["username", "email"],
        password_stage: $password_stage,
        case_insensitive_matching: true,
        show_matched_user: true,
        sources: $sources,
        show_source_labels: true,
        pretend_user_exists: false,
        enable_remember_me: false
      }'
  )"

  if [[ -n "$existing" ]]; then
    stage_pk="$(printf '%s\n' "$existing" | jq -r '.pk')"
    api PATCH "/api/v3/stages/identification/${stage_pk}/" "$payload" >/dev/null
  else
    stage_pk="$(
      api POST "/api/v3/stages/identification/" "$payload" \
        | jq -r '.pk // empty'
    )"
  fi

  printf '%s\n' "$stage_pk"
}

ensure_user_login_stage() {
  local existing payload stage_pk

  existing="$(lookup_stage_by_name "/api/v3/stages/user_login/" "$user_login_stage_name")"
  payload="$(
    jq -cn \
      --arg name "$user_login_stage_name" \
      '{
        name: $name,
        session_duration: "hours=12",
        terminate_other_sessions: false,
        remember_me_offset: "seconds=0",
        network_binding: "no_binding",
        geoip_binding: "no_binding"
      }'
  )"

  if [[ -n "$existing" ]]; then
    stage_pk="$(printf '%s\n' "$existing" | jq -r '.pk')"
    api PATCH "/api/v3/stages/user_login/${stage_pk}/" "$payload" >/dev/null
  else
    stage_pk="$(
      api POST "/api/v3/stages/user_login/" "$payload" \
        | jq -r '.pk // empty'
    )"
  fi

  printf '%s\n' "$stage_pk"
}

ensure_authentication_flow() {
  local existing_pk payload

  existing_pk="$(lookup_flow_pk "$authentication_flow_slug")"
  payload="$(
    jq -cn \
      --arg name "$authentication_flow_name" \
      --arg slug "$authentication_flow_slug" \
      '{
        name: $name,
        title: $name,
        slug: $slug,
        designation: "authentication",
        policy_engine_mode: "any",
        layout: "stacked"
      }'
  )"

  if [[ -n "$existing_pk" ]]; then
    api PATCH "/api/v3/flows/instances/${authentication_flow_slug}/" "$payload" >/dev/null
    printf '%s\n' "$existing_pk"
  else
    api POST "/api/v3/flows/instances/" "$payload" \
      | jq -r '.pk // empty'
  fi
}

ensure_flow_binding() {
  local flow_pk="$1"
  local stage_pk="$2"
  local order="$3"
  local existing payload binding_pk

  existing="$(
    api GET "/api/v3/flows/bindings/?target=${flow_pk}&stage=${stage_pk}&page_size=200" \
      | jq -c '.results[]?' \
      | head -n1
  )"

  payload="$(
    jq -cn \
      --arg target "$flow_pk" \
      --arg stage "$stage_pk" \
      --argjson order "$order" \
      '{
        target: $target,
        stage: $stage,
        order: $order,
        policy_engine_mode: "any"
      }'
  )"

  if [[ -n "$existing" ]]; then
    binding_pk="$(printf '%s\n' "$existing" | jq -r '.pk')"
    api PATCH "/api/v3/flows/bindings/${binding_pk}/" "$payload" >/dev/null
  else
    api POST "/api/v3/flows/bindings/" "$payload" >/dev/null
  fi
}

wait_for_authentik

mapfile -t provider_pks < <(
  api GET "/api/v3/providers/oauth2/?page_size=200" \
    | jq -r --argjson provider_slugs "$provider_slugs_json" '
        .results[]?
        | select(
            ((.assigned_application_slug // empty) as $assigned | ($provider_slugs | index($assigned)) != null)
            or ((.slug // empty) as $slug | ($provider_slugs | index($slug)) != null)
          )
        | .pk // empty
      '
)

if [[ "${#provider_pks[@]}" -eq 0 ]]; then
  echo "error: could not resolve any Authentik Tailnet OAuth providers from ${provider_slugs_json}" >&2
  exit 1
fi

google_source_pk="$(lookup_source_pk "$google_source_slug" || true)"
password_stage_pk="$(ensure_password_stage)"
identification_stage_pk="$(ensure_identification_stage "$password_stage_pk" "$google_source_pk")"
user_login_stage_pk="$(ensure_user_login_stage)"
authentication_flow_pk="$(ensure_authentication_flow)"

ensure_flow_binding "$authentication_flow_pk" "$identification_stage_pk" 10
ensure_flow_binding "$authentication_flow_pk" "$user_login_stage_pk" 30

for provider_pk in "${provider_pks[@]}"; do
  api PATCH "/api/v3/providers/oauth2/${provider_pk}/" "$(
    jq -cn --arg flow "$authentication_flow_pk" '{authentication_flow: $flow}'
  )" >/dev/null
done

echo "Synced Burrow Tailnet authentication flow for providers ${provider_slugs_json}."
