#!/bin/sh
set -e

# Cameleer SaaS — Bootstrap Script
# Creates Logto apps, users, organizations, roles.
# Seeds cameleer_saas DB with tenant, environment, license.
# Configures cameleer3-server OIDC.
# Idempotent: checks existence before creating.

LOGTO_ENDPOINT="${LOGTO_ENDPOINT:-http://logto:3001}"
LOGTO_ADMIN_ENDPOINT="${LOGTO_ADMIN_ENDPOINT:-http://logto:3002}"
LOGTO_PUBLIC_ENDPOINT="${LOGTO_PUBLIC_ENDPOINT:-http://localhost:3001}"
MGMT_API_RESOURCE="https://default.logto.app/api"
BOOTSTRAP_FILE="/data/logto-bootstrap.json"
PG_HOST="${PG_HOST:-postgres}"
PG_USER="${PG_USER:-cameleer}"
PG_DB_LOGTO="logto"
PG_DB_SAAS="${PG_DB_SAAS:-cameleer_saas}"

# App names
SPA_APP_NAME="Cameleer SaaS"
M2M_APP_NAME="Cameleer SaaS Backend"
TRAD_APP_NAME="Cameleer Dashboard"
API_RESOURCE_INDICATOR="https://api.cameleer.local"
API_RESOURCE_NAME="Cameleer SaaS API"

# Users (configurable via env vars)
SAAS_ADMIN_USER="${SAAS_ADMIN_USER:-admin}"
SAAS_ADMIN_PASS="${SAAS_ADMIN_PASS:-admin}"
TENANT_ADMIN_USER="${TENANT_ADMIN_USER:-camel}"
TENANT_ADMIN_PASS="${TENANT_ADMIN_PASS:-camel}"

# Tenant config
TENANT_NAME="Example Tenant"
TENANT_SLUG="default"
BOOTSTRAP_TOKEN="${CAMELEER_AUTH_TOKEN:-default-bootstrap-token}"

# Vendor seed (optional — creates saas-vendor role + vendor user)
VENDOR_SEED_ENABLED="${VENDOR_SEED_ENABLED:-false}"
VENDOR_USER="${VENDOR_USER:-vendor}"
VENDOR_PASS="${VENDOR_PASS:-vendor}"
VENDOR_NAME="${VENDOR_NAME:-SaaS Vendor}"

# Server config
SERVER_ENDPOINT="${SERVER_ENDPOINT:-http://cameleer3-server:8081}"
SERVER_UI_USER="${SERVER_UI_USER:-admin}"
SERVER_UI_PASS="${SERVER_UI_PASS:-admin}"

# Redirect URIs (derived from PUBLIC_HOST and PUBLIC_PROTOCOL)
HOST="${PUBLIC_HOST:-localhost}"
PROTO="${PUBLIC_PROTOCOL:-https}"
SPA_REDIRECT_URIS="[\"${PROTO}://${HOST}/platform/callback\"]"
SPA_POST_LOGOUT_URIS="[\"${PROTO}://${HOST}/platform/login\"]"
TRAD_REDIRECT_URIS="[\"${PROTO}://${HOST}/oidc/callback\",\"${PROTO}://${HOST}/server/oidc/callback\"]"
TRAD_POST_LOGOUT_URIS="[\"${PROTO}://${HOST}\",\"${PROTO}://${HOST}/server\",\"${PROTO}://${HOST}/server/login?local\"]"

log() { echo "[bootstrap] $1"; }
pgpass() { PGPASSWORD="${PG_PASSWORD:-cameleer_dev}"; export PGPASSWORD; }

# Install jq + curl
apk add --no-cache jq curl >/dev/null 2>&1

# Read cached secrets from previous run
if [ -f "$BOOTSTRAP_FILE" ]; then
  CACHED_M2M_SECRET=$(jq -r '.m2mClientSecret // empty' "$BOOTSTRAP_FILE" 2>/dev/null)
  CACHED_TRAD_SECRET=$(jq -r '.tradAppSecret // empty' "$BOOTSTRAP_FILE" 2>/dev/null)
  CACHED_SPA_ID=$(jq -r '.spaClientId // empty' "$BOOTSTRAP_FILE" 2>/dev/null)
  log "Found cached bootstrap file"
  if [ -n "$CACHED_M2M_SECRET" ] && [ -n "$CACHED_SPA_ID" ]; then
    log "Bootstrap already complete — skipping. Delete $BOOTSTRAP_FILE to force re-run."
    exit 0
  fi
fi

# ============================================================
# PHASE 1: Wait for services
# ============================================================

log "Waiting for Logto..."
for i in $(seq 1 60); do
  if curl -sf "${LOGTO_ENDPOINT}/oidc/.well-known/openid-configuration" >/dev/null 2>&1; then
    log "Logto is ready."
    break
  fi
  [ "$i" -eq 60 ] && { log "ERROR: Logto not ready after 60s"; exit 1; }
  sleep 1
done

log "Waiting for cameleer3-server..."
for i in $(seq 1 60); do
  if curl -sf "${SERVER_ENDPOINT}/api/v1/health" >/dev/null 2>&1; then
    log "cameleer3-server is ready."
    break
  fi
  [ "$i" -eq 60 ] && { log "WARNING: cameleer3-server not ready after 60s — skipping OIDC config"; }
  sleep 1
done

# ============================================================
# PHASE 2: Get Management API token
# ============================================================

log "Reading m-default secret from database..."
pgpass
M_DEFAULT_SECRET=$(psql -h "$PG_HOST" -U "$PG_USER" -d "$PG_DB_LOGTO" -t -A -c \
  "SELECT secret FROM applications WHERE id = 'm-default' AND tenant_id = 'admin';")
[ -z "$M_DEFAULT_SECRET" ] && { log "ERROR: m-default app not found"; exit 1; }

get_admin_token() {
  curl -s -X POST "${LOGTO_ADMIN_ENDPOINT}/oidc/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -H "Host: ${HOST}:3002" \
    -H "X-Forwarded-Proto: https" \
    -d "grant_type=client_credentials&client_id=${1}&client_secret=${2}&resource=${MGMT_API_RESOURCE}&scope=all"
}

get_default_token() {
  curl -s -X POST "${LOGTO_ENDPOINT}/oidc/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -H "Host: ${HOST}" \
    -d "grant_type=client_credentials&client_id=${1}&client_secret=${2}&resource=${MGMT_API_RESOURCE}&scope=all"
}

log "Getting Management API token..."
TOKEN_RESPONSE=$(get_admin_token "m-default" "$M_DEFAULT_SECRET")
TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token' 2>/dev/null)
[ -z "$TOKEN" ] || [ "$TOKEN" = "null" ] && { log "ERROR: Failed to get token"; exit 1; }
log "Got Management API token."

# Verify Management API is fully ready (Logto may still be initializing internally)
log "Verifying Management API is responsive..."
for i in $(seq 1 30); do
  VERIFY_RESPONSE=$(curl -s -H "Authorization: Bearer $TOKEN" -H "Host: ${HOST}" "${LOGTO_ENDPOINT}/api/roles" 2>/dev/null)
  if echo "$VERIFY_RESPONSE" | jq -e 'type == "array"' >/dev/null 2>&1; then
    log "Management API is ready."
    break
  fi
  [ "$i" -eq 30 ] && { log "ERROR: Management API not responsive after 30s"; exit 1; }
  sleep 1
done

# --- Helper: Logto API calls ---
api_get() {
  curl -s -H "Authorization: Bearer $TOKEN" -H "Host: ${HOST}" "${LOGTO_ENDPOINT}${1}" 2>/dev/null || echo "[]"
}
api_post() {
  curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Host: ${HOST}" \
    -d "$2" "${LOGTO_ENDPOINT}${1}" 2>/dev/null || true
}
api_put() {
  curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Host: ${HOST}" \
    -d "$2" "${LOGTO_ENDPOINT}${1}" 2>/dev/null || true
}
api_delete() {
  curl -s -X DELETE -H "Authorization: Bearer $TOKEN" -H "Host: ${HOST}" "${LOGTO_ENDPOINT}${1}" 2>/dev/null || true
}
api_patch() {
  curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Host: ${HOST}" \
    -d "$2" "${LOGTO_ENDPOINT}${1}" 2>/dev/null || true
}

# ============================================================
# PHASE 3: Create Logto applications
# ============================================================

EXISTING_APPS=$(api_get "/api/applications")

# --- SPA app (for SaaS frontend) ---
SPA_ID=$(echo "$EXISTING_APPS" | jq -r ".[] | select(.name == \"$SPA_APP_NAME\" and .type == \"SPA\") | .id")
if [ -n "$SPA_ID" ]; then
  log "SPA app exists: $SPA_ID"
else
  log "Creating SPA app..."
  SPA_RESPONSE=$(api_post "/api/applications" "{
    \"name\": \"$SPA_APP_NAME\",
    \"type\": \"SPA\",
    \"oidcClientMetadata\": {
      \"redirectUris\": $SPA_REDIRECT_URIS,
      \"postLogoutRedirectUris\": $SPA_POST_LOGOUT_URIS
    }
  }")
  SPA_ID=$(echo "$SPA_RESPONSE" | jq -r '.id')
  log "Created SPA app: $SPA_ID"
fi

# --- Traditional Web App (for cameleer3-server OIDC) ---
TRAD_ID=$(echo "$EXISTING_APPS" | jq -r ".[] | select(.name == \"$TRAD_APP_NAME\" and .type == \"Traditional\") | .id")
TRAD_SECRET=""
if [ -n "$TRAD_ID" ]; then
  log "Traditional app exists: $TRAD_ID"
  TRAD_SECRET="${CACHED_TRAD_SECRET:-}"
else
  log "Creating Traditional Web app..."
  TRAD_RESPONSE=$(api_post "/api/applications" "{
    \"name\": \"$TRAD_APP_NAME\",
    \"type\": \"Traditional\",
    \"oidcClientMetadata\": {
      \"redirectUris\": $TRAD_REDIRECT_URIS,
      \"postLogoutRedirectUris\": $TRAD_POST_LOGOUT_URIS
    }
  }")
  TRAD_ID=$(echo "$TRAD_RESPONSE" | jq -r '.id')
  TRAD_SECRET=$(echo "$TRAD_RESPONSE" | jq -r '.secret')
  [ "$TRAD_SECRET" = "null" ] && TRAD_SECRET=""
  log "Created Traditional app: $TRAD_ID"
fi

# Enable skip consent for the Traditional app (first-party SSO)
api_put "/api/applications/$TRAD_ID" '{"isThirdParty": false, "customClientMetadata": {"alwaysIssueRefreshToken": true, "skipConsent": true}}' >/dev/null 2>&1
log "Traditional app: skip consent enabled."

# --- API resource ---
EXISTING_RESOURCES=$(api_get "/api/resources")
API_RESOURCE_ID=$(echo "$EXISTING_RESOURCES" | jq -r ".[] | select(.indicator == \"$API_RESOURCE_INDICATOR\") | .id")
if [ -n "$API_RESOURCE_ID" ]; then
  log "API resource exists: $API_RESOURCE_ID"
else
  log "Creating API resource..."
  RESOURCE_RESPONSE=$(api_post "/api/resources" "{
    \"name\": \"$API_RESOURCE_NAME\",
    \"indicator\": \"$API_RESOURCE_INDICATOR\"
  }")
  API_RESOURCE_ID=$(echo "$RESOURCE_RESPONSE" | jq -r '.id')
  log "Created API resource: $API_RESOURCE_ID"
fi

# ============================================================
# PHASE 3b: Create API resource scopes
# ============================================================

log "Creating API resource scopes..."
EXISTING_SCOPES=$(api_get "/api/resources/${API_RESOURCE_ID}/scopes")

create_scope() {
  local name="$1"
  local desc="$2"
  local existing_id=$(echo "$EXISTING_SCOPES" | jq -r ".[] | select(.name == \"$name\") | .id")
  if [ -n "$existing_id" ]; then
    log "  Scope '$name' exists: $existing_id" >&2
    echo "$existing_id"
  else
    local resp=$(api_post "/api/resources/${API_RESOURCE_ID}/scopes" "{\"name\": \"$name\", \"description\": \"$desc\"}")
    local new_id=$(echo "$resp" | jq -r '.id')
    log "  Created scope '$name': $new_id" >&2
    echo "$new_id"
  fi
}

# Platform-level scope
SCOPE_PLATFORM_ADMIN=$(create_scope "platform:admin" "SaaS platform administration")

# Tenant-level scopes
SCOPE_TENANT_MANAGE=$(create_scope "tenant:manage" "Manage tenant settings")
SCOPE_BILLING_MANAGE=$(create_scope "billing:manage" "Manage billing")
SCOPE_TEAM_MANAGE=$(create_scope "team:manage" "Manage team members")
SCOPE_APPS_MANAGE=$(create_scope "apps:manage" "Create and delete apps")
SCOPE_APPS_DEPLOY=$(create_scope "apps:deploy" "Deploy apps")
SCOPE_SECRETS_MANAGE=$(create_scope "secrets:manage" "Manage secrets")
SCOPE_OBSERVE_READ=$(create_scope "observe:read" "View observability data")
SCOPE_OBSERVE_DEBUG=$(create_scope "observe:debug" "Debug and replay operations")
SCOPE_SETTINGS_MANAGE=$(create_scope "settings:manage" "Manage settings")

# Server-level scopes (mapped to server RBAC roles via JWT scope claim)
SCOPE_SERVER_ADMIN=$(create_scope "server:admin" "Full server access")
SCOPE_SERVER_OPERATOR=$(create_scope "server:operator" "Deploy and manage apps in server")
SCOPE_SERVER_VIEWER=$(create_scope "server:viewer" "Read-only server observability")

# Collect scope IDs for role assignment
# Owner: full tenant control
OWNER_SCOPE_IDS="\"$SCOPE_TENANT_MANAGE\",\"$SCOPE_BILLING_MANAGE\",\"$SCOPE_TEAM_MANAGE\",\"$SCOPE_APPS_MANAGE\",\"$SCOPE_APPS_DEPLOY\",\"$SCOPE_SECRETS_MANAGE\",\"$SCOPE_OBSERVE_READ\",\"$SCOPE_OBSERVE_DEBUG\",\"$SCOPE_SETTINGS_MANAGE\",\"$SCOPE_SERVER_ADMIN\""
# Operator: app lifecycle + observability (no billing/team/secrets/settings)
OPERATOR_SCOPE_IDS="\"$SCOPE_APPS_MANAGE\",\"$SCOPE_APPS_DEPLOY\",\"$SCOPE_OBSERVE_READ\",\"$SCOPE_OBSERVE_DEBUG\",\"$SCOPE_SERVER_OPERATOR\""
# Viewer: read-only observability
VIEWER_SCOPE_IDS="\"$SCOPE_OBSERVE_READ\",\"$SCOPE_SERVER_VIEWER\""
# Vendor (saas-vendor global role): platform:admin + all tenant scopes
ALL_SCOPE_IDS="\"$SCOPE_PLATFORM_ADMIN\",$OWNER_SCOPE_IDS"

# --- M2M app ---
M2M_ID=$(echo "$EXISTING_APPS" | jq -r ".[] | select(.name == \"$M2M_APP_NAME\" and .type == \"MachineToMachine\") | .id")
M2M_SECRET=""
if [ -n "$M2M_ID" ]; then
  log "M2M app exists: $M2M_ID"
  M2M_SECRET="${CACHED_M2M_SECRET:-}"
else
  log "Creating M2M app..."
  M2M_RESPONSE=$(api_post "/api/applications" "{
    \"name\": \"$M2M_APP_NAME\",
    \"type\": \"MachineToMachine\"
  }")
  M2M_ID=$(echo "$M2M_RESPONSE" | jq -r '.id')
  M2M_SECRET=$(echo "$M2M_RESPONSE" | jq -r '.secret')
  log "Created M2M app: $M2M_ID"

  # Assign Management API role
  log "Assigning Management API access to M2M app..."
  pgpass
  MGMT_RESOURCE_ID=$(psql -h "$PG_HOST" -U "$PG_USER" -d "$PG_DB_LOGTO" -t -A -c \
    "SELECT id FROM resources WHERE indicator = '$MGMT_API_RESOURCE' AND tenant_id = 'default';")

  if [ -n "$MGMT_RESOURCE_ID" ]; then
    SCOPE_IDS=$(psql -h "$PG_HOST" -U "$PG_USER" -d "$PG_DB_LOGTO" -t -A -c \
      "SELECT json_agg(id) FROM scopes WHERE resource_id = '$MGMT_RESOURCE_ID' AND tenant_id = 'default';" | tr -d '[:space:]')

    ROLE_RESPONSE=$(api_post "/api/roles" "{
      \"name\": \"cameleer-m2m-management\",
      \"description\": \"Full Management API access for Cameleer SaaS\",
      \"type\": \"MachineToMachine\",
      \"scopeIds\": $SCOPE_IDS
    }")
    ROLE_ID=$(echo "$ROLE_RESPONSE" | jq -r '.id')

    if [ -n "$ROLE_ID" ] && [ "$ROLE_ID" != "null" ]; then
      api_post "/api/roles/$ROLE_ID/applications" "{\"applicationIds\": [\"$M2M_ID\"]}" >/dev/null
      log "Assigned Management API role to M2M app."

      VERIFY=$(get_default_token "$M2M_ID" "$M2M_SECRET")
      VERIFY_TOKEN=$(echo "$VERIFY" | jq -r '.access_token')
      if [ -n "$VERIFY_TOKEN" ] && [ "$VERIFY_TOKEN" != "null" ]; then
        log "Verified M2M app works."
      else
        log "WARNING: M2M verification failed"
        M2M_SECRET=""
      fi
    fi
  fi

fi

# Create M2M role for the Cameleer API resource (server:admin access) — idempotent
EXISTING_M2M_SERVER_ROLE=$(api_get "/api/roles" | jq -r '.[] | select(.name == "cameleer-m2m-server") | .id')
if [ -z "$EXISTING_M2M_SERVER_ROLE" ]; then
  log "Creating M2M server access role..."
  SERVER_M2M_ROLE_RESPONSE=$(api_post "/api/roles" "{
    \"name\": \"cameleer-m2m-server\",
    \"description\": \"Server API access for SaaS backend (M2M)\",
    \"type\": \"MachineToMachine\",
    \"scopeIds\": [\"$SCOPE_SERVER_ADMIN\"]
  }")
  EXISTING_M2M_SERVER_ROLE=$(echo "$SERVER_M2M_ROLE_RESPONSE" | jq -r '.id')
fi
if [ -n "$EXISTING_M2M_SERVER_ROLE" ] && [ "$EXISTING_M2M_SERVER_ROLE" != "null" ] && [ -n "$M2M_ID" ]; then
  api_post "/api/roles/$EXISTING_M2M_SERVER_ROLE/applications" "{\"applicationIds\": [\"$M2M_ID\"]}" >/dev/null 2>&1
  log "Assigned server API role to M2M app: $EXISTING_M2M_SERVER_ROLE"
fi

# ============================================================
# PHASE 4: Create roles
# ============================================================

# --- Organization roles: owner, operator, viewer ---
# Note: platform-admin / saas-vendor global role is NOT created here.
# It is injected via docker/vendor-seed.sh on the hosted SaaS environment only.
log "Creating organization roles..."
EXISTING_ORG_ROLES=$(api_get "/api/organization-roles")

ORG_OWNER_ROLE_ID=$(echo "$EXISTING_ORG_ROLES" | jq -r '.[] | select(.name == "owner") | .id')
if [ -n "$ORG_OWNER_ROLE_ID" ]; then
  log "Org owner role exists: $ORG_OWNER_ROLE_ID"
else
  ORG_OWNER_RESPONSE=$(api_post "/api/organization-roles" "{
    \"name\": \"owner\",
    \"description\": \"Platform owner — full tenant control\"
  }")
  ORG_OWNER_ROLE_ID=$(echo "$ORG_OWNER_RESPONSE" | jq -r '.id')
  log "Created org owner role: $ORG_OWNER_ROLE_ID"
fi

ORG_OPERATOR_ROLE_ID=$(echo "$EXISTING_ORG_ROLES" | jq -r '.[] | select(.name == "operator") | .id')
if [ -z "$ORG_OPERATOR_ROLE_ID" ]; then
  ORG_OPERATOR_RESPONSE=$(api_post "/api/organization-roles" "{
    \"name\": \"operator\",
    \"description\": \"Operator — manage apps, deploy, observe\"
  }")
  ORG_OPERATOR_ROLE_ID=$(echo "$ORG_OPERATOR_RESPONSE" | jq -r '.id')
  log "Created org operator role: $ORG_OPERATOR_ROLE_ID"
fi

ORG_VIEWER_ROLE_ID=$(echo "$EXISTING_ORG_ROLES" | jq -r '.[] | select(.name == "viewer") | .id')
if [ -z "$ORG_VIEWER_ROLE_ID" ]; then
  ORG_VIEWER_RESPONSE=$(api_post "/api/organization-roles" "{
    \"name\": \"viewer\",
    \"description\": \"Viewer — read-only observability\"
  }")
  ORG_VIEWER_ROLE_ID=$(echo "$ORG_VIEWER_RESPONSE" | jq -r '.id')
  log "Created org viewer role: $ORG_VIEWER_ROLE_ID"
fi

# Assign API resource scopes to org roles (these appear in org-scoped resource tokens)
log "Assigning API resource scopes to organization roles..."
api_put "/api/organization-roles/${ORG_OWNER_ROLE_ID}/resource-scopes" "{\"scopeIds\": [$OWNER_SCOPE_IDS]}" >/dev/null 2>&1
api_put "/api/organization-roles/${ORG_OPERATOR_ROLE_ID}/resource-scopes" "{\"scopeIds\": [$OPERATOR_SCOPE_IDS]}" >/dev/null 2>&1
api_put "/api/organization-roles/${ORG_VIEWER_ROLE_ID}/resource-scopes" "{\"scopeIds\": [$VIEWER_SCOPE_IDS]}" >/dev/null 2>&1
log "API resource scopes assigned to organization roles."

# ============================================================
# PHASE 5: Create users
# ============================================================

# --- Platform Owner ---
log "Checking for platform owner user '$SAAS_ADMIN_USER'..."
ADMIN_USER_ID=$(api_get "/api/users?search=$SAAS_ADMIN_USER" | jq -r ".[] | select(.username == \"$SAAS_ADMIN_USER\") | .id")
if [ -n "$ADMIN_USER_ID" ]; then
  log "Platform owner exists: $ADMIN_USER_ID"
else
  log "Creating platform owner '$SAAS_ADMIN_USER'..."
  ADMIN_RESPONSE=$(api_post "/api/users" "{
    \"username\": \"$SAAS_ADMIN_USER\",
    \"password\": \"$SAAS_ADMIN_PASS\",
    \"name\": \"Platform Owner\"
  }")
  ADMIN_USER_ID=$(echo "$ADMIN_RESPONSE" | jq -r '.id')
  log "Created platform owner: $ADMIN_USER_ID"
  # No global role assigned — owner role is org-scoped.
  # SaaS vendor role is injected via docker/vendor-seed.sh on hosted environments.
fi

# --- Grant SaaS admin Logto console access (admin tenant, port 3002) ---
log "Granting SaaS admin Logto console access..."

# Get admin-tenant M2M token (m-default token has wrong audience for port 3002)
ADMIN_MGMT_RESOURCE="https://admin.logto.app/api"
log "Reading m-admin secret from database..."
M_ADMIN_SECRET=$(psql -h "$PG_HOST" -U "$PG_USER" -d "$PG_DB_LOGTO" -t -A -c \
  "SELECT secret FROM applications WHERE id = 'm-admin' AND tenant_id = 'admin';" 2>/dev/null)

if [ -z "$M_ADMIN_SECRET" ]; then
  log "WARNING: m-admin app not found — skipping console access"
else
  ADMIN_TOKEN_RESPONSE=$(curl -s -X POST "${LOGTO_ADMIN_ENDPOINT}/oidc/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -H "Host: ${HOST}:3002" \
    -H "X-Forwarded-Proto: https" \
    -d "grant_type=client_credentials&client_id=m-admin&client_secret=${M_ADMIN_SECRET}&resource=${ADMIN_MGMT_RESOURCE}&scope=all")
  ADMIN_TOKEN=$(echo "$ADMIN_TOKEN_RESPONSE" | jq -r '.access_token' 2>/dev/null)

  if [ -z "$ADMIN_TOKEN" ] || [ "$ADMIN_TOKEN" = "null" ]; then
    log "WARNING: Failed to get admin tenant token — skipping console access"
    log "Response: $(echo "$ADMIN_TOKEN_RESPONSE" | head -c 200)"
  else
    log "Got admin tenant token."

    # Admin-tenant API helpers (port 3002, admin token)
    admin_api_get() {
      curl -s -H "Authorization: Bearer $ADMIN_TOKEN" -H "Host: ${HOST}:3002" -H "X-Forwarded-Proto: https" "${LOGTO_ADMIN_ENDPOINT}${1}" 2>/dev/null || echo "[]"
    }
    admin_api_post() {
      curl -s -X POST -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" -H "Host: ${HOST}:3002" -H "X-Forwarded-Proto: https" \
        -d "$2" "${LOGTO_ADMIN_ENDPOINT}${1}" 2>/dev/null || true
    }
    admin_api_patch() {
      curl -s -X PATCH -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" -H "Host: ${HOST}:3002" -H "X-Forwarded-Proto: https" \
        -d "$2" "${LOGTO_ADMIN_ENDPOINT}${1}" 2>/dev/null || true
    }

    # Check if admin user already exists on admin tenant
    ADMIN_TENANT_USER_ID=$(admin_api_get "/api/users?search=$SAAS_ADMIN_USER" | jq -r ".[] | select(.username == \"$SAAS_ADMIN_USER\") | .id" 2>/dev/null)
if [ -z "$ADMIN_TENANT_USER_ID" ] || [ "$ADMIN_TENANT_USER_ID" = "null" ]; then
  log "Creating admin console user '$SAAS_ADMIN_USER'..."
  ADMIN_TENANT_RESPONSE=$(admin_api_post "/api/users" "{
    \"username\": \"$SAAS_ADMIN_USER\",
    \"password\": \"$SAAS_ADMIN_PASS\",
    \"name\": \"Platform Admin\"
  }")
  ADMIN_TENANT_USER_ID=$(echo "$ADMIN_TENANT_RESPONSE" | jq -r '.id')
  log "Created admin console user: $ADMIN_TENANT_USER_ID"
else
  log "Admin console user exists: $ADMIN_TENANT_USER_ID"
fi

if [ -n "$ADMIN_TENANT_USER_ID" ] && [ "$ADMIN_TENANT_USER_ID" != "null" ]; then
  # Assign both 'user' (required base role) and 'default:admin' (Management API access)
  ADMIN_USER_ROLE_ID=$(admin_api_get "/api/roles" | jq -r '.[] | select(.name == "user") | .id')
  ADMIN_ROLE_ID=$(admin_api_get "/api/roles" | jq -r '.[] | select(.name == "default:admin") | .id')
  ROLE_IDS_JSON="[]"
  if [ -n "$ADMIN_USER_ROLE_ID" ] && [ "$ADMIN_USER_ROLE_ID" != "null" ]; then
    ROLE_IDS_JSON=$(echo "$ROLE_IDS_JSON" | jq ". + [\"$ADMIN_USER_ROLE_ID\"]")
  fi
  if [ -n "$ADMIN_ROLE_ID" ] && [ "$ADMIN_ROLE_ID" != "null" ]; then
    ROLE_IDS_JSON=$(echo "$ROLE_IDS_JSON" | jq ". + [\"$ADMIN_ROLE_ID\"]")
  fi
  if [ "$ROLE_IDS_JSON" != "[]" ]; then
    admin_api_post "/api/users/$ADMIN_TENANT_USER_ID/roles" "{\"roleIds\": $ROLE_IDS_JSON}" >/dev/null 2>&1
    log "Assigned admin tenant roles (user + default:admin)."
  else
    log "WARNING: admin tenant roles not found"
  fi

  # Add to t-default organization with admin role
  admin_api_post "/api/organizations/t-default/users" "{\"userIds\": [\"$ADMIN_TENANT_USER_ID\"]}" >/dev/null 2>&1
  TENANT_ADMIN_ORG_ROLE_ID=$(admin_api_get "/api/organization-roles" | jq -r '.[] | select(.name == "admin") | .id')
  if [ -n "$TENANT_ADMIN_ORG_ROLE_ID" ] && [ "$TENANT_ADMIN_ORG_ROLE_ID" != "null" ]; then
    admin_api_post "/api/organizations/t-default/users/$ADMIN_TENANT_USER_ID/roles" "{\"organizationRoleIds\": [\"$TENANT_ADMIN_ORG_ROLE_ID\"]}" >/dev/null 2>&1
    log "Added to t-default organization with admin role."
  fi
  # Switch admin tenant sign-in mode from Register to SignIn (user already created)
  admin_api_patch "/api/sign-in-exp" '{"signInMode": "SignIn"}' >/dev/null 2>&1
  log "Set admin tenant sign-in mode to SignIn."

  log "SaaS admin granted Logto console access."
else
  log "WARNING: Could not create admin console user"
fi

  fi  # end: ADMIN_TOKEN check
fi  # end: M_ADMIN_SECRET check

# --- Viewer user (for testing read-only OIDC role in server) ---
log "Checking for viewer user '$TENANT_ADMIN_USER'..."
TENANT_USER_ID=$(api_get "/api/users?search=$TENANT_ADMIN_USER" | jq -r ".[] | select(.username == \"$TENANT_ADMIN_USER\") | .id")
if [ -n "$TENANT_USER_ID" ]; then
  log "Viewer user exists: $TENANT_USER_ID"
else
  log "Creating viewer user '$TENANT_ADMIN_USER'..."
  TENANT_RESPONSE=$(api_post "/api/users" "{
    \"username\": \"$TENANT_ADMIN_USER\",
    \"password\": \"$TENANT_ADMIN_PASS\",
    \"name\": \"Viewer\"
  }")
  TENANT_USER_ID=$(echo "$TENANT_RESPONSE" | jq -r '.id')
  log "Created viewer user: $TENANT_USER_ID"
fi

# ============================================================
# PHASE 6: Create organization + add users
# ============================================================

log "Checking for organization '$TENANT_NAME'..."
EXISTING_ORGS=$(api_get "/api/organizations")
ORG_ID=$(echo "$EXISTING_ORGS" | jq -r ".[] | select(.name == \"$TENANT_NAME\") | .id")

if [ -n "$ORG_ID" ]; then
  log "Organization exists: $ORG_ID"
else
  log "Creating organization '$TENANT_NAME'..."
  ORG_RESPONSE=$(api_post "/api/organizations" "{
    \"name\": \"$TENANT_NAME\",
    \"description\": \"Bootstrap demo tenant\"
  }")
  ORG_ID=$(echo "$ORG_RESPONSE" | jq -r '.id')
  log "Created organization: $ORG_ID"
fi

# Add users to organization
if [ -n "$ADMIN_USER_ID" ] && [ "$ADMIN_USER_ID" != "null" ]; then
  log "Adding platform owner to organization..."
  api_post "/api/organizations/$ORG_ID/users" "{\"userIds\": [\"$ADMIN_USER_ID\"]}" >/dev/null 2>&1
  api_put "/api/organizations/$ORG_ID/users/$ADMIN_USER_ID/roles" "{\"organizationRoleIds\": [\"$ORG_OWNER_ROLE_ID\"]}" >/dev/null 2>&1
  log "Platform owner added to org with owner role."
fi

if [ -n "$TENANT_USER_ID" ] && [ "$TENANT_USER_ID" != "null" ]; then
  log "Adding viewer user to organization..."
  api_post "/api/organizations/$ORG_ID/users" "{\"userIds\": [\"$TENANT_USER_ID\"]}" >/dev/null 2>&1
  api_put "/api/organizations/$ORG_ID/users/$TENANT_USER_ID/roles" "{\"organizationRoleIds\": [\"$ORG_VIEWER_ROLE_ID\"]}" >/dev/null 2>&1
  log "Viewer user added to org with viewer role."
fi

# ============================================================
# PHASE 7: Configure cameleer3-server OIDC
# ============================================================

SERVER_HEALTHY="no"
for i in 1 2 3; do
  if curl -sf "${SERVER_ENDPOINT}/api/v1/health" >/dev/null 2>&1; then
    SERVER_HEALTHY="yes"
    break
  fi
  sleep 2
done
log "Phase 7 check: SERVER_HEALTHY=$SERVER_HEALTHY, TRAD_SECRET length=${#TRAD_SECRET}"

if [ "$SERVER_HEALTHY" = "yes" ] && [ -n "$TRAD_SECRET" ]; then
  log "Configuring cameleer3-server OIDC..."

  # Login to server as admin
  SERVER_TOKEN_RESPONSE=$(curl -s -X POST "${SERVER_ENDPOINT}/api/v1/auth/login" \
    -H "Content-Type: application/json" \
    -d "{\"username\": \"$SERVER_UI_USER\", \"password\": \"$SERVER_UI_PASS\"}")
  SERVER_TOKEN=$(echo "$SERVER_TOKEN_RESPONSE" | jq -r '.accessToken' 2>/dev/null)

  if [ -n "$SERVER_TOKEN" ] && [ "$SERVER_TOKEN" != "null" ]; then
    # Configure OIDC
    OIDC_RESPONSE=$(curl -s -X PUT "${SERVER_ENDPOINT}/api/v1/admin/oidc" \
      -H "Authorization: Bearer $SERVER_TOKEN" \
      -H "Content-Type: application/json" \
      -d "{
        \"enabled\": true,
        \"issuerUri\": \"$LOGTO_PUBLIC_ENDPOINT/oidc\",
        \"clientId\": \"$TRAD_ID\",
        \"clientSecret\": \"$TRAD_SECRET\",
        \"autoSignup\": true,
        \"defaultRoles\": [\"VIEWER\"],
        \"displayNameClaim\": \"name\",
        \"rolesClaim\": \"roles\",
        \"audience\": \"$API_RESOURCE_INDICATOR\",
        \"additionalScopes\": []
      }")
    log "OIDC config response: $(echo "$OIDC_RESPONSE" | head -c 200)"
    log "cameleer3-server OIDC configured."

    # Seed claim mapping rules (roles → server RBAC)
    log "Seeding claim mapping rules..."
    EXISTING_MAPPINGS=$(curl -s -H "Authorization: Bearer $SERVER_TOKEN" \
      "${SERVER_ENDPOINT}/api/v1/admin/claim-mappings" 2>/dev/null || echo "[]")

    seed_claim_mapping() {
      local match_value="$1"
      local target="$2"
      local priority="$3"
      local exists=$(echo "$EXISTING_MAPPINGS" | jq -r ".[] | select(.matchValue == \"$match_value\") | .id")
      if [ -n "$exists" ]; then
        log "  Claim mapping '$match_value' → $target exists"
      else
        local resp=$(curl -s -X POST "${SERVER_ENDPOINT}/api/v1/admin/claim-mappings" \
          -H "Authorization: Bearer $SERVER_TOKEN" \
          -H "Content-Type: application/json" \
          -d "{\"claim\":\"roles\",\"matchType\":\"contains\",\"matchValue\":\"$match_value\",\"action\":\"assignRole\",\"target\":\"$target\",\"priority\":$priority}")
        log "  Created claim mapping '$match_value' → $target"
      fi
    }

    seed_claim_mapping "server:admin" "ADMIN" 10
    seed_claim_mapping "server:operator" "OPERATOR" 20
    log "Claim mapping rules seeded."
  else
    log "WARNING: Could not login to cameleer3-server — skipping OIDC config"
  fi
else
  log "WARNING: cameleer3-server not available or no Traditional app secret — skipping OIDC config"
fi

# ============================================================
# PHASE 7b: Configure Logto Custom JWT for access tokens
# ============================================================
# Adds a 'roles' claim to access tokens based on user's org roles and global roles.
# This allows the server to extract roles from the access token using rolesClaim: "roles".

log "Configuring Logto Custom JWT for access tokens..."
CUSTOM_JWT_SCRIPT='const getCustomJwtClaims = async ({ token, context, environmentVariables }) => {
  const roleMap = { owner: "server:admin", operator: "server:operator", viewer: "server:viewer" };
  const roles = new Set();
  if (context?.user?.organizationRoles) {
    for (const orgRole of context.user.organizationRoles) {
      const mapped = roleMap[orgRole.roleName];
      if (mapped) roles.add(mapped);
    }
  }
  if (context?.user?.roles) {
    for (const role of context.user.roles) {
      if (role.name === "saas-vendor") roles.add("server:admin");
    }
  }
  return roles.size > 0 ? { roles: [...roles] } : {};
};'

CUSTOM_JWT_PAYLOAD=$(jq -n --arg script "$CUSTOM_JWT_SCRIPT" '{ script: $script }')
CUSTOM_JWT_RESPONSE=$(api_put "/api/configs/jwt-customizer/access-token" "$CUSTOM_JWT_PAYLOAD" 2>&1)
if echo "$CUSTOM_JWT_RESPONSE" | jq -e '.script' >/dev/null 2>&1; then
  log "Custom JWT configured for access tokens."
else
  log "WARNING: Custom JWT configuration failed — server OIDC login may fall back to local roles"
  log "Response: $(echo "$CUSTOM_JWT_RESPONSE" | head -c 200)"
fi

# ============================================================
# PHASE 8: Configure sign-in branding
# ============================================================

log "Configuring sign-in experience branding..."
api_patch "/api/sign-in-exp" "{
  \"color\": {
    \"primaryColor\": \"#C6820E\",
    \"isDarkModeEnabled\": true,
    \"darkPrimaryColor\": \"#D4941E\"
  },
  \"branding\": {
    \"logoUrl\": \"${PROTO}://${HOST}/platform/logo.svg\",
    \"darkLogoUrl\": \"${PROTO}://${HOST}/platform/logo-dark.svg\"
  }
}"
log "Sign-in branding configured."

# ============================================================
# PHASE 9: Cleanup seeded apps
# ============================================================

if [ -n "$M2M_SECRET" ]; then
  log "Cleaning up seeded apps with known secrets..."
  for SEEDED_ID in "m-default" "m-admin" "s6cz3wajdv8gtdyz8e941"; do
    if echo "$EXISTING_APPS" | jq -e ".[] | select(.id == \"$SEEDED_ID\")" >/dev/null 2>&1; then
      api_delete "/api/applications/$SEEDED_ID"
      log "Deleted seeded app: $SEEDED_ID"
    fi
  done
fi

# ============================================================
# PHASE 10: Write bootstrap results
# ============================================================

log "Writing bootstrap config to $BOOTSTRAP_FILE..."
mkdir -p "$(dirname "$BOOTSTRAP_FILE")"
cat > "$BOOTSTRAP_FILE" <<EOF
{
  "spaClientId": "$SPA_ID",
  "m2mClientId": "$M2M_ID",
  "m2mClientSecret": "$M2M_SECRET",
  "tradAppId": "$TRAD_ID",
  "tradAppSecret": "$TRAD_SECRET",
  "apiResourceIndicator": "$API_RESOURCE_INDICATOR",
  "organizationId": "$ORG_ID",
  "tenantName": "$TENANT_NAME",
  "tenantSlug": "$TENANT_SLUG",
  "bootstrapToken": "$BOOTSTRAP_TOKEN",
  "platformAdminUser": "$SAAS_ADMIN_USER",
  "tenantAdminUser": "$TENANT_ADMIN_USER",
  "oidcIssuerUri": "${LOGTO_ENDPOINT}/oidc",
  "oidcAudience": "$API_RESOURCE_INDICATOR"
}
EOF
chmod 644 "$BOOTSTRAP_FILE"

# ============================================================
# Phase 12: Vendor Seed (optional)
# ============================================================

if [ "$VENDOR_SEED_ENABLED" = "true" ]; then
  log ""
  log "=== Phase 12: Vendor Seed ==="

  # Create saas-vendor global role with all API scopes
  log "Checking for saas-vendor role..."
  EXISTING_ROLES=$(api_get "/api/roles")
  VENDOR_ROLE_ID=$(echo "$EXISTING_ROLES" | jq -r '.[] | select(.name == "saas-vendor" and .type == "User") | .id')

  if [ -n "$VENDOR_ROLE_ID" ]; then
    log "saas-vendor role exists: $VENDOR_ROLE_ID"
  else
    ALL_SCOPE_IDS=$(api_get "/api/resources/$API_RESOURCE_ID/scopes" | jq '[.[].id]')
    log "Creating saas-vendor role with all scopes..."
    VENDOR_ROLE_RESPONSE=$(api_post "/api/roles" "{
      \"name\": \"saas-vendor\",
      \"description\": \"SaaS vendor — full platform control across all tenants\",
      \"type\": \"User\",
      \"scopeIds\": $ALL_SCOPE_IDS
    }")
    VENDOR_ROLE_ID=$(echo "$VENDOR_ROLE_RESPONSE" | jq -r '.id')
    log "Created saas-vendor role: $VENDOR_ROLE_ID"
  fi

  # Create vendor user
  log "Checking for vendor user '$VENDOR_USER'..."
  VENDOR_USER_ID=$(api_get "/api/users?search=$VENDOR_USER" | jq -r ".[] | select(.username == \"$VENDOR_USER\") | .id")

  if [ -n "$VENDOR_USER_ID" ]; then
    log "Vendor user exists: $VENDOR_USER_ID"
  else
    log "Creating vendor user '$VENDOR_USER'..."
    VENDOR_RESPONSE=$(api_post "/api/users" "{
      \"username\": \"$VENDOR_USER\",
      \"password\": \"$VENDOR_PASS\",
      \"name\": \"$VENDOR_NAME\"
    }")
    VENDOR_USER_ID=$(echo "$VENDOR_RESPONSE" | jq -r '.id')
    log "Created vendor user: $VENDOR_USER_ID"
  fi

  # Assign saas-vendor role
  if [ -n "$VENDOR_ROLE_ID" ] && [ "$VENDOR_ROLE_ID" != "null" ]; then
    api_post "/api/users/$VENDOR_USER_ID/roles" "{\"roleIds\": [\"$VENDOR_ROLE_ID\"]}" >/dev/null 2>&1
    log "Assigned saas-vendor role globally."
  fi

  # Add vendor to all existing organizations with owner role
  log "Adding vendor to all organizations..."
  ORG_OWNER_ROLE_ID=$(api_get "/api/organization-roles" | jq -r '.[] | select(.name == "owner") | .id')
  ORGS=$(api_get "/api/organizations")
  ORG_COUNT=$(echo "$ORGS" | jq 'length')

  for i in $(seq 0 $((ORG_COUNT - 1))); do
    SEED_ORG_ID=$(echo "$ORGS" | jq -r ".[$i].id")
    SEED_ORG_NAME=$(echo "$ORGS" | jq -r ".[$i].name")
    api_post "/api/organizations/$SEED_ORG_ID/users" "{\"userIds\": [\"$VENDOR_USER_ID\"]}" >/dev/null 2>&1
    if [ -n "$ORG_OWNER_ROLE_ID" ] && [ "$ORG_OWNER_ROLE_ID" != "null" ]; then
      curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
        -d "{\"organizationRoleIds\": [\"$ORG_OWNER_ROLE_ID\"]}" \
        "${LOGTO_ENDPOINT}/api/organizations/$SEED_ORG_ID/users/$VENDOR_USER_ID/roles" >/dev/null 2>&1
    fi
    log "  Added to org '$SEED_ORG_NAME' with owner role."
  done

  log "Vendor seed complete."
fi

log ""
log "=== Bootstrap complete! ==="
# dev only — remove credential logging in production
log "  Platform Owner: $SAAS_ADMIN_USER / $SAAS_ADMIN_PASS  (org role: owner)"
log "  Viewer:         $TENANT_ADMIN_USER / $TENANT_ADMIN_PASS  (org role: viewer)"
log "  Tenant:         $TENANT_NAME (slug: $TENANT_SLUG)"
log "  Organization:   $ORG_ID"
log "  SPA Client ID:  $SPA_ID"
if [ "$VENDOR_SEED_ENABLED" = "true" ]; then
  log "  Vendor:         $VENDOR_USER / $VENDOR_PASS  (role: saas-vendor)"
fi
log ""
