#!/usr/bin/env bash
# Verify a Dynatrace tenant is ready for the Enablement app BEFORE registering it.
# Creates nothing permanent: every probe cleans up after itself.
#
#   ./check-tenant-setup.sh <client-id> <client-secret> <account-urn> <tenant-url>
#
# Exit 0 = ready. Exit 1 = something is missing (each failure is named).

set -uo pipefail
CID="${1:?client id}"; CSEC="${2:?client secret}"; ACCT="${3:?urn:dtaccount:...}"; TURL="${4:?https://<env>.apps.dynatrace.com}"
ENVID=$(sed -E 's#https?://([^.]+)\..*#\1#' <<<"$TURL")
APPS="${TURL%%/}"; APPS="${APPS%/}"
ENVURN="urn:dtenvironment:${ENVID}"
PROXY="${APPS}/platform/classic/environment-api"
case "$APPS" in
  *sprint*) SSO="https://sso-sprint.dynatracelabs.com/sso/oauth2/token"
            ACCT_API="https://api-hardening.internal.dynatracelabs.com"
            LIVE="${APPS/.sprint.apps./.sprint.}" ;;   # <env>.sprint.dynatracelabs.com — there is no sprint .live.
  *dev*)    SSO="https://sso-dev.dynatracelabs.com/sso/oauth2/token"
            ACCT_API="https://api-hardening.internal.dynatracelabs.com"
            LIVE="${APPS/.dev.apps./.dev.}" ;;
  *)        SSO="https://sso.dynatrace.com/sso/oauth2/token"
            ACCT_API="https://api.dynatrace.com"
            LIVE="${APPS/.apps./.live.}" ;;
esac
PASS=0; FAIL=0; WARN=0
ok(){ printf '  \033[32mPASS\033[0m  %s\n' "$1"; PASS=$((PASS+1)); }
warn(){ printf '  \033[33mSKIP\033[0m  %s\n' "$1"; WARN=$((WARN+1)); }
no(){ printf '  \033[31mFAIL\033[0m  %s\n' "$1"; FAIL=$((FAIL+1)); }

bearer(){ curl -s -X POST "$SSO" -d grant_type=client_credentials -d "client_id=$CID" \
  -d "client_secret=$CSEC" -d "scope=$1" -d "resource=$2" \
  | python3 -c 'import sys,json;print(json.load(sys.stdin).get("access_token",""))' 2>/dev/null; }

echo "Tenant : $TURL   (env $ENVID)"
echo "Client : $CID"
echo
echo "1. Scopes the client is granted"
for s in app-engine:apps:install app-engine:apps:run app-engine:apps:delete \
         settings:objects:read settings:objects:write app-settings:objects:read \
         environment-api:api-tokens:read environment-api:api-tokens:write \
         environment-api:activegate-tokens:write \
         document:documents:read document:documents:write document:documents:delete \
         document:documents:admin; do
  [ -n "$(bearer "$s" "$ENVURN")" ] && ok "$s" || no "$s  <-- add this scope to the OAuth client"
done
for s in platform-token:tokens:write platform-token:tokens:manage; do
  [ -n "$(bearer "$s" "$ACCT")" ] && ok "$s (account)" || no "$s (account)  <-- add this scope"
done

echo
echo "2. Capabilities that actually matter (a granted scope is not proof)"
EXP=$(python3 -c 'import datetime;print((datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%S.000Z"))')

# Learner-token tier: classic dt0c01 is the primary path; on environments where classic
# creation has been retired (HTTP 400, rolled out per environment) fall back to a live
# platform-token probe. Branch on HTTP status only — error text is not a contract.
TIER=""
B=$(bearer "environment-api:api-tokens:write" "$ENVURN")
if [ -z "$B" ]; then no "mint a learner token — skipped, scope not granted (see above)"; B=x; fi
R=$(curl -s -w '\n%{http_code}' -X POST "$PROXY/v2/apiTokens" -H "Authorization: Bearer $B" -H 'Content-Type: application/json' \
  -d "{\"name\":\"orbital-preflight\",\"expirationDate\":\"$EXP\",\"scopes\":[\"InstallerDownload\",\"activeGateTokenManagement.create\",\"entities.read\",\"settings.read\",\"settings.write\",\"DataExport\"]}")
MC=$(tail -n1 <<<"$R"); R=$(sed '$d' <<<"$R")
TID=$(python3 -c 'import sys,json;print(json.load(sys.stdin).get("id",""))' <<<"$R" 2>/dev/null)
TOK=$(python3 -c 'import sys,json;print(json.load(sys.stdin).get("token",""))' <<<"$R" 2>/dev/null)
if [ -n "$TOK" ]; then
  TIER="classic"
  ok "mint a learner token (classic dt0c01)"
  # Classic tokens authenticate against the classic (live) domain only — the
  # /platform/classic proxy rejects them with 401.
  C=$(curl -s -o /dev/null -w '%{http_code}' "$LIVE/api/v1/deployment/installer/agent/connectioninfo" -H "Authorization: Api-Token $TOK")
  if   [ "$C" = 200 ]; then ok "that token can install OneAgent/ActiveGate (HTTP 200)"
  elif [ "$C" = 000 ]; then warn "OneAgent/ActiveGate check skipped — $LIVE not reachable from here"
  else no "token minted but Operator calls fail (HTTP $C)"; fi
  curl -s -o /dev/null -X DELETE "$PROXY/v2/apiTokens/$TID" -H "Authorization: Bearer $B"
elif [ "$B" != x ] && [ "$MC" = 400 ]; then
  warn "classic token creation retired on this environment (HTTP 400) — probing the platform-token path"
  PB=$(bearer "platform-token:tokens:write platform-token:tokens:manage" "$ACCT")
  if [ -z "$PB" ]; then
    no "gen3 fallback impossible: client cannot obtain platform-token:tokens:write/manage (see above)"
  else
    ACCTID="${ACCT##*:}"
    PR=$(curl -s -X POST "$ACCT_API/iam/v1/accounts/$ACCTID/platform-tokens" -H "Authorization: Bearer $PB" -H 'Content-Type: application/json' \
      -d "{\"name\":\"orbital-preflight-gen3\",\"scope\":[\"fleet-management:oneagents:download\",\"fleet-management:oneagent.connection-info:read\",\"fleet-management:activegate.connection-info:read\",\"fleet-management:container-images:read\",\"fleet-management:activegate.tokens:create\",\"fleet-management:activegate.tokens:write\",\"settings:objects:read\",\"settings:objects:write\",\"storage:entities:read\",\"storage:logs:write\",\"storage:events:write\",\"storage:metrics:write\"],\"resource\":[\"$ENVURN\"],\"tags\":[\"enablement-preflight\"],\"expirationDate\":\"$EXP\"}")
    PTOK=$(python3 -c 'import sys,json;print(json.load(sys.stdin).get("token",""))' <<<"$PR" 2>/dev/null)
    PTID=$(python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("tokenId") or d.get("id") or "")' <<<"$PR" 2>/dev/null)
    if [ -z "$PTOK" ]; then
      no "cannot mint a platform token: $(head -c 150 <<<"$PR")"
    else
      C=$(curl -s -o /dev/null -w '%{http_code}' "$PROXY/v1/deployment/installer/agent/connectioninfo" -H "Authorization: Api-Token $PTOK")
      if [ "$C" = 200 ]; then
        TIER="gen3"
        ok "mint a learner token (gen3 platform dt0s16) + Operator install check (HTTP 200)"
      else
        # Scopes are on the token but its OWNER's IAM policy cannot exercise them —
        # detectable only by this live call, never by reading the token record back.
        no "platform token minted but Operator calls fail (HTTP $C) — the token owner's IAM policy lacks the permissions; bind environment:roles:manage-settings at environment level to the user who created the OAuth client, then re-run"
      fi
      [ -n "$PTID" ] && curl -s -o /dev/null -X DELETE "$ACCT_API/iam/v1/accounts/$ACCTID/platform-tokens/$PTID" -H "Authorization: Bearer $PB"
    fi
  fi
elif [ "$B" != x ]; then
  no "cannot mint a learner token (HTTP $MC): $(head -c 150 <<<"$R")"
fi

B=$(bearer "environment-api:activegate-tokens:write" "$ENVURN")
R=$(curl -s -X POST "$PROXY/v2/activeGateTokens" -H "Authorization: Bearer $B" \
  -H 'Content-Type: application/json' -d "{\"name\":\"orbital-preflight-ag\",\"activeGateType\":\"ENVIRONMENT\",\"expirationDate\":\"$EXP\"}")
AGID=$(python3 -c 'import sys,json;print(json.load(sys.stdin).get("id",""))' <<<"$R" 2>/dev/null)
if [ -n "$AGID" ]; then
  ok "mint an ActiveGate token"
  curl -s -o /dev/null -X DELETE "$PROXY/v2/activeGateTokens/$AGID" -H "Authorization: Bearer $B"
else
  no "cannot mint an ActiveGate token: $(head -c 120 <<<"$R")"
fi

B=$(bearer "settings:objects:read settings:objects:write" "$ENVURN")
C=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$PROXY/v2/settings/objects?validateOnly=true" \
  -H "Authorization: Bearer $B" -H 'Content-Type: application/json' \
  -d '[{"schemaId":"builtin:management-zones","scope":"environment","value":{"name":"orbital-preflight","rules":[]}}]')
if   [ "$C" = 200 ]; then ok "write environment settings (allowlist, telemetry, store the client)"
elif [ "$C" = 404 ]; then warn "settings-write check skipped — probe schema not present on this environment"
else no "cannot write environment settings (HTTP $C)"; fi

B=$(bearer "document:documents:read document:documents:write document:documents:delete document:documents:admin" "$ENVURN")
if [ -z "$B" ]; then no "store training content — skipped, document scopes not granted (see above)"; B=x; fi
BND="----orbital$$"
DOC=$(printf -- "--%s\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\norbital-preflight-doc\r\n--%s\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\ndashboard\r\n--%s\r\nContent-Disposition: form-data; name=\"content\"; filename=\"content\"\r\nContent-Type: application/json\r\n\r\n{}\r\n--%s--\r\n" "$BND" "$BND" "$BND" "$BND" \
  | curl -s -X POST "$APPS/platform/document/v1/documents" -H "Authorization: Bearer $B" \
      -H "Content-Type: multipart/form-data; boundary=$BND" --data-binary @-)
DID=$(python3 -c 'import sys,json;print(json.load(sys.stdin).get("id",""))' <<<"$DOC" 2>/dev/null)
if [ -n "$DID" ]; then
  OWN=$(python3 -c 'import sys,json;print(json.load(sys.stdin).get("owner",""))' <<<"$DOC" 2>/dev/null)
  ok "store training content (owner: ${OWN:0:8}… — one identity, no duplicate copies)"
  V=$(curl -s "$APPS/platform/document/v1/documents?filter=id%20%3D%20%27$DID%27&page-size=1" -H "Authorization: Bearer $B" \
      | python3 -c 'import sys,json;d=json.load(sys.stdin).get("documents",[]);print(d[0]["version"] if d else 1)' 2>/dev/null)
  curl -s -o /dev/null -X DELETE "$APPS/platform/document/v1/documents/$DID?optimistic-locking-version=$V" -H "Authorization: Bearer $B"
elif [ "$B" != x ]; then
  no "cannot store training content: $(head -c 150 <<<"$DOC")"
fi

echo
if [ "$FAIL" -eq 0 ]; then
  case "$TIER" in
    classic) T=" Learner tokens: classic (dt0c01)." ;;
    gen3)    T=" Learner tokens: gen3 platform path (classic creation is retired here)." ;;
    *)       T="" ;;
  esac
  echo "READY — $PASS checks passed${WARN:+, $WARN skipped}.${T} Register this tenant in Orbital."; exit 0
else echo "NOT READY — $FAIL of $((PASS+FAIL)) checks failed. Fix the scopes above and re-run."; exit 1; fi
