#!/usr/bin/env bash
# Archera onboarding for Azure
#
# This script grants Archera (https://archera.ai) the access it needs to read your
# Azure usage and commitment data, and to manage Reservations / Savings Plans on
# your behalf. It will:
#   1. Provision the Archera enterprise app in your tenant (one-time consent).
#   2. Create a tenant-scope custom role with read-only billing/usage permissions.
#   3. Create a resource group and storage account to hold cost exports.
#   4. Assign the custom role + a few built-in Azure roles to the Archera app.
#
# You'll be prompted to:
#   - Confirm your tenant ID and the subscription that will host the storage
#     account ("management subscription").
#   - Pick which subscriptions to onboard from the eligible list.
#   - Approve resource creation before anything is provisioned.
#
# Prerequisites:
#   - Azure CLI: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli
#   - whiptail (preinstalled on most Linux; macOS: `brew install newt`)
#   - You must be signed in: `az login --tenant <your-tenant-id>`
#   - Your account needs:
#       * Owner (or equivalent) on the management subscription
#       * Permission to create custom roles at tenant root (User Access
#         Administrator at the root management group — typically requires the
#         "Access management for Azure resources" toggle in Microsoft Entra ID).
#
# Supported tenant types: direct subscription onboarding only (Pay-As-You-Go,
# Enterprise Agreement, CSP, MSDN/DevTest). Microsoft Customer Agreement billing
# account onboarding is not supported by this script.
#
# Questions: contact your Archera support representative.

set -euo pipefail

main() {
  # Dependencies
  command -v az >/dev/null || { echo "az CLI not installed (https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)"; exit 1; }
  command -v whiptail >/dev/null || { echo "whiptail not installed (install via 'brew install newt' on macOS or 'apt-get install whiptail' on Linux)"; exit 1; }

  # User inputs and validation
  read -rp "Tenant ID: " TENANT_ID
  read -rp "Management subscription ID: " MANAGEMENT_SUB_ID
  read -rp "Azure region [eastus]: " REGION
  REGION="${REGION:-eastus}"
  validate_inputs

  # Pin az session to the management subscription
  az account set --subscription "$MANAGEMENT_SUB_ID"

  # Default resource names (recommended)
  SUFFIX="${MANAGEMENT_SUB_ID%%-*}"
  ROLE_NAME="archerarole${SUFFIX}"
  RG="archeraresource${SUFFIX}"
  STORAGE="archerastorage${SUFFIX}"
  STORAGE_SCOPE="/subscriptions/${MANAGEMENT_SUB_ID}/resourceGroups/${RG}/providers/Microsoft.Storage/storageAccounts/${STORAGE}"

  # Create new or fetch existing Archera.ai Enterprise Application
  echo "Resolving Archera enterprise application..."
  ARCHERA_APP_CLIENT_ID="f6e3844b-18dc-479d-ad28-4359dd5b7286"
  ARCHERA_APP=$(az ad sp show --id "$ARCHERA_APP_CLIENT_ID" --query id -o tsv 2>/dev/null \
    || az ad sp create --id "$ARCHERA_APP_CLIENT_ID" --query id -o tsv)
  [[ -n "$ARCHERA_APP" ]] || { echo "Could not resolve Archera SP. Ensure admin consent is granted in tenant $TENANT_ID."; exit 1; }

  # Select eligible subscriptions
  select_subscriptions

  # Show full plan and ask for one approval before any provisioning
  echo
  echo "===================================================================="
  echo "  About to apply the following changes:"
  echo "===================================================================="
  echo "  Tenant ID:               $TENANT_ID"
  echo "  Management subscription: $MANAGEMENT_SUB_ID"
  echo "  Region:                  $REGION"
  echo "  Custom role:             $ROLE_NAME (tenant scope)"
  echo "  Resource group:          $RG"
  echo "  Storage account:         $STORAGE"
  echo "  Custom role assigned to subscriptions:"
  printf "    - %s\n" "${SUBSCRIPTIONS[@]}"
  echo "  Built-in roles assigned to Archera SP:"
  echo "    - Reader and Data Access, Storage Blob Data Reader, User Access Administrator (storage scope)"
  echo "    - Reservation Reader / Purchaser (capacity + management sub)"
  echo "    - Savings Plan Reader / Purchaser (billingbenefits + management sub)"
  echo "    - Advisor Recommendations Contributor (advisor scope)"
  echo "===================================================================="
  confirm "Proceed?"

  # Register resource providers
  echo "Registering Microsoft.Storage provider (may take ~15 seconds)..."
  az provider register --namespace Microsoft.Storage --wait
  echo "Registering Microsoft.CostManagementExports provider (may take ~15 seconds)..."
  az provider register --namespace Microsoft.CostManagementExports --wait

  # Custom role and per-subscription assignment
  create_custom_role
  for sub_id in "${SUBSCRIPTIONS[@]}"; do
    assign_role "$ROLE_NAME" "/subscriptions/${sub_id}"
  done

  # Resource group and storage account
  az group create --name "$RG" --location "$REGION"
  echo "Creating storage account '$STORAGE' (this may take up to 30 seconds)..."
  local sa_output sa_rc=0
  sa_output=$(az storage account create \
    --name "$STORAGE" --resource-group "$RG" --location "$REGION" \
    --sku Standard_LRS --kind StorageV2 \
    --min-tls-version TLS1_2 \
    --allow-blob-public-access false \
    --require-infrastructure-encryption true \
    --encryption-services blob file 2>&1) || sa_rc=$?
  echo "$sa_output"
  if [[ $sa_rc -ne 0 ]]; then
    if grep -q -E "msrestazure|No module named" <<< "$sa_output"; then
      echo
      echo "This is a known issue with the 'storage-preview' az extension."
      echo "Fix: az extension remove --name storage-preview"
      echo "Then re-run this script."
    fi
    exit "$sa_rc"
  fi

  # Storage role assignments
  echo "Assigning roles (this may take a moment)..."
  READER_AND_DATA_ACCESS_ROLE_ID="c12c1c16-33a1-487b-954d-41c89c60f349"
  STORAGE_BLOB_DATA_READER_ROLE_ID="2a2b9908-6ea1-4ae2-8e65-a410df84e7d1"
  USER_ACCESS_ADMIN_ROLE_ID="18d7d88d-d35e-4fb5-a5c3-7773c20a72d9"
  assign_role "$READER_AND_DATA_ACCESS_ROLE_ID" "$STORAGE_SCOPE"
  assign_role "$STORAGE_BLOB_DATA_READER_ROLE_ID" "$STORAGE_SCOPE"
  assign_role "$USER_ACCESS_ADMIN_ROLE_ID" "$STORAGE_SCOPE"

  # Reservations role assignments
  RESERVATION_SCOPE="/providers/Microsoft.Capacity"
  RESERVATION_READER_ROLE_ID="582fc458-8989-419f-a480-75249bc5db7e"
  RESERVATION_PURCHASER_ROLE_ID="f7b75c60-3036-4b75-91c3-6b41c27c1689"
  assign_role "$RESERVATION_READER_ROLE_ID" "$RESERVATION_SCOPE"
  assign_role "$RESERVATION_PURCHASER_ROLE_ID" "$RESERVATION_SCOPE"
  assign_role "$RESERVATION_PURCHASER_ROLE_ID" "/subscriptions/${MANAGEMENT_SUB_ID}"

  # Savings plans role assignments
  SAVINGSPLAN_SCOPE="/providers/Microsoft.BillingBenefits"
  SAVINGSPLAN_READER_ROLE_ID="d534ad90-4ac5-4815-a178-b2e47397baab"
  SAVINGSPLAN_PURCHASER_ROLE_ID="3d24a3a0-c154-4f6f-a5ed-adc8e01ddb74"
  assign_role "$SAVINGSPLAN_READER_ROLE_ID" "$SAVINGSPLAN_SCOPE"
  assign_role "$SAVINGSPLAN_PURCHASER_ROLE_ID" "$SAVINGSPLAN_SCOPE"
  assign_role "$SAVINGSPLAN_PURCHASER_ROLE_ID" "/subscriptions/${MANAGEMENT_SUB_ID}"

  # Advisor role assignments
  ADVISOR_SCOPE="/providers/Microsoft.Advisor"
  ADVISOR_RECOMMENDATIONS_CONTRIBUTOR_ROLE_ID="6b534d80-e337-47c4-864f-140f5c7f593d"
  assign_role "$ADVISOR_RECOMMENDATIONS_CONTRIBUTOR_ROLE_ID" "$ADVISOR_SCOPE"

  # Summary
  echo
  echo "===================================================================="
  echo "  Archera Azure onboarding complete"
  echo "===================================================================="
  echo "  Tenant ID:              $TENANT_ID"
  echo "  Management subscription: $MANAGEMENT_SUB_ID"
  echo "  Resource group:         $RG"
  echo "  Storage account:        $STORAGE"
  echo "  Custom role:            $ROLE_NAME"
  echo "  Onboarded subscriptions:"
  printf "    - %s\n" "${SUBSCRIPTIONS[@]}"
  echo
  echo "  Next step: notify your Archera support contact that onboarding is"
  echo "  complete and share the tenant + subscription IDs above."
  echo "===================================================================="
}

confirm() {
  local reply
  read -rp "$1 [y/Y]: " reply
  [[ "$reply" == "y" || "$reply" == "Y" ]] || { echo "Aborted."; exit 1; }
}

# Validates $TENANT_ID and $MANAGEMENT_SUB_ID format, az session tenant, and sub-in-tenant.
validate_inputs() {
  local uuid_re='^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
  local active_tenant sub_tenant
  [[ "$TENANT_ID"        =~ $uuid_re ]] || { echo "TENANT_ID must be a lowercase UUID, got: '$TENANT_ID'"; exit 1; }
  [[ "$MANAGEMENT_SUB_ID" =~ $uuid_re ]] || { echo "MANAGEMENT_SUB_ID must be a lowercase UUID, got: '$MANAGEMENT_SUB_ID'"; exit 1; }
  active_tenant=$(az account show --query tenantId -o tsv 2>/dev/null || true)
  [[ "$TENANT_ID" == "$active_tenant" ]] || { echo "TENANT_ID '$TENANT_ID' does not match active az session tenant '$active_tenant'. Run 'az login --tenant $TENANT_ID'."; exit 1; }
  sub_tenant=$(az account show --subscription "$MANAGEMENT_SUB_ID" --query tenantId -o tsv 2>/dev/null || true)
  [[ "$sub_tenant" == "$TENANT_ID" ]] || { echo "Subscription '$MANAGEMENT_SUB_ID' not found in tenant '$TENANT_ID'."; exit 1; }
}

# Idempotent role assignment for $ARCHERA_APP. Args: <role-id> <scope>
assign_role() {
  local role_id="$1" scope="$2" output rc=0
  output=$(az role assignment create --assignee-object-id "$ARCHERA_APP" --assignee-principal-type ServicePrincipal \
    --role "$role_id" --scope "$scope" 2>&1) || rc=$?
  if [[ $rc -eq 0 ]]; then
    return 0
  fi
  if grep -q -E "RoleAssignmentExists|already exists" <<< "$output"; then
    echo "Role $role_id already assigned at $scope — skipping."
    return 0
  fi
  echo "$output" >&2
  return $rc
}

# Creates the Archera custom role at tenant scope; skip-on-exists, never updates.
create_custom_role() {
  local description="Archera Custom Role v2"
  local actions='[
    "*/read",
    "Microsoft.Consumption/*",
    "Microsoft.CostManagement/*",
    "Microsoft.Billing/billingPeriods/read",
    "Microsoft.Billing/billingProperty/read",
    "Microsoft.Resources/subscriptions/read",
    "Microsoft.Resources/subscriptions/resourceGroups/read",
    "Microsoft.Advisor/configurations/read",
    "Microsoft.Advisor/recommendations/read",
    "Microsoft.Management/managementGroups/read"
  ]'
  local tenant_scope="/providers/Microsoft.Management/managementGroups/${TENANT_ID}"
  local existing_id role_def_json
  echo "Checking for existing custom role..."
  existing_id=$(az role definition list --name "$ROLE_NAME" --scope "$tenant_scope" --query "[0].id" -o tsv)
  if [[ -n "$existing_id" ]]; then
    echo "Custom role '$ROLE_NAME' already exists at $tenant_scope — skipping."
    return 0
  fi
  echo "Creating custom role at tenant scope (may take ~15 seconds)..."
  role_def_json=$(cat <<EOF
{
  "name": "${ROLE_NAME}",
  "roleName": "${ROLE_NAME}",
  "description": "${description}",
  "type": "CustomRole",
  "actions": ${actions},
  "notActions": [],
  "dataActions": [],
  "notDataActions": [],
  "assignableScopes": ["${tenant_scope}"]
}
EOF
)
  az role definition create --role-definition "$role_def_json"
}

# Discovers eligible subs (FOCUS-supported quota IDs, state=Enabled)
select_subscriptions() {
  local quotas="['PayAsYouGo_2014-09-01','EnterpriseAgreement_2014-09-01','CSP_2015-05-01','CSP_MG_2017-12-01','MSDNDevTest_2014-09-01']"
  local eligible=() line id name quota status checklist=() selected
  echo "Fetching eligible subscriptions..."
  while IFS= read -r line; do
    eligible+=( "$line" )
  done < <(az rest --method get \
    --url "https://management.azure.com/subscriptions?api-version=2020-01-01" \
    --query "value[?state=='Enabled' && contains(${quotas}, subscriptionPolicies.quotaId)].[subscriptionId, displayName, subscriptionPolicies.quotaId]" \
    -o tsv)

  [[ ${#eligible[@]} -gt 0 ]] || { echo "No eligible subscriptions found in tenant $TENANT_ID"; exit 1; }

  for line in "${eligible[@]}"; do
    IFS=$'\t' read -r id name quota <<< "$line"
    status="OFF"
    [[ "$id" == "$MANAGEMENT_SUB_ID" ]] && status="ON"
    checklist+=( "$id" "$name ($quota)" "$status" )
  done

  selected=$(whiptail --title "Archera Azure Onboarding" --separate-output \
    --checklist "Use 'Arrow Keys and Space Bar' to select, 'Tab Key' to OK/Cancel, 'Enter Key' to confirm." \
    0 0 "${#eligible[@]}" "${checklist[@]}" \
    3>&1 1>&2 2>&3) || { echo "Selection cancelled"; exit 1; }

  SUBSCRIPTIONS=()
  while IFS= read -r line; do
    SUBSCRIPTIONS+=( "$line" )
  done <<< "$selected"
  [[ ${#SUBSCRIPTIONS[@]} -gt 0 ]] || { echo "No subscriptions selected"; exit 1; }
}

main "$@"
