#!/bin/bash
# Takumi Guard token provisioning
# Usage: TG_BOT_API_KEY=... ./takumi-guard-setup-{VERSION}.sh <BOT_ID> <USER_IDENTIFIER> [SCOPES]
#
# Mints a new org user token and configures package managers to use Takumi Guard.
# Designed to be called directly or via an MDM deployment wrapper script.
#
# Supported:
#   npm ecosystem -- npm, pnpm, yarn v2+, bun
#   PyPI ecosystem -- pip, uv, poetry
#
# Arguments:
#   BOT_ID          Required. Bot ID from Shisho Cloud console.
#   USER_IDENTIFIER Required. Unique device/user identifier.
#                   Allowed characters: a-z, A-Z, 0-9, hyphen, underscore, dot.
#                   Length: 4-255 characters.
#   SCOPES          Optional. Comma-separated ecosystems to configure (default: npm,pypi).
#
# Environment:
#   TG_BOT_API_KEY  Required. Bot API key from Shisho Cloud console.
#                   Passed via env var to avoid shell history / process table exposure.
#   TG_PREMINTED_TOKEN  Optional. Pre-minted org token to use instead of calling the API.
#                       Validated against tg_org_ format before use.
#
# Idempotency:
#   This script is safe to run multiple times. If a tg_org_* token is already
#   present in any config file, the script reuses it without minting a new token.
#   Config files for the specified scopes are still updated if needed.
#
# Backup and rollback:
#   Before modifying an existing config file, a timestamped backup is created
#   next to the original (e.g. ~/.npmrc-backup-20260408-162351). These backups
#   are preserved even if the script succeeds, so you can manually restore the
#   previous state at any time by copying the backup file back:
#
#     cp ~/.npmrc-backup-20260408-162351 ~/.npmrc
#
#   If the script fails midway through (e.g. a network error during token mint,
#   or a file write error), all changes made so far are automatically rolled back
#   to the pre-execution state. No manual intervention is needed.
#
# Config file handling:
#   - If a config file already exists, it is updated regardless of whether the
#     corresponding tool is installed. This is intentional: package managers do
#     not remove their config files on uninstall, and pre-placing config ensures
#     Guard is active as soon as the tool is (re)installed.
#   - If a config file does not exist, it is created only if the tool is installed.
#   - Non-Guard settings in existing config files (e.g. ignore-scripts, min-release-age)
#     are preserved.
#
# Prerequisites:
#   curl must be installed on the target machine.

set -euE

# ---------------------------------------------------------------------------
# Prerequisites
# ---------------------------------------------------------------------------

if ! command -v curl >/dev/null 2>&1; then
  echo "[Error] Required command not found: curl" >&2
  exit 1
fi

# ---------------------------------------------------------------------------
# Argument parsing and validation
# ---------------------------------------------------------------------------

BOT_ID="${1:?Usage: TG_BOT_API_KEY=... setup.sh BOT_ID USER_IDENTIFIER [SCOPES]}"
USER_IDENTIFIER="${2:?Usage: TG_BOT_API_KEY=... setup.sh BOT_ID USER_IDENTIFIER [SCOPES]}"
API_KEY="${TG_BOT_API_KEY:?Set TG_BOT_API_KEY environment variable}"
SCOPES="${3:-npm,pypi}"

has_scope() {
  echo ",$SCOPES," | grep -q ",$1,"
}

# Validate that a value contains only safe characters for JSON string embedding.
# Prevents JSON injection when constructing request bodies.
validate_safe_string() {
  local label="$1"
  local value="$2"
  if ! printf '%s\n' "$value" | grep -qE '^[0-9a-zA-Z._-]+$'; then
    echo "[Error] $label contains invalid characters" >&2
    return 1
  fi
}

validate_user_identifier() {
  local id="$1"
  local len
  len=$(printf '%s' "$id" | wc -c | tr -d ' ')
  if [ "$len" -lt 4 ] || [ "$len" -gt 255 ]; then
    echo "[Error] USER_IDENTIFIER must be 4-255 characters (got $len)" >&2
    return 1
  fi
  validate_safe_string "USER_IDENTIFIER" "$id"
}

validate_user_identifier "$USER_IDENTIFIER"
validate_safe_string "BOT_ID" "$BOT_ID"
validate_safe_string "TG_BOT_API_KEY" "$API_KEY"

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

# Portable in-place file edit (works on both BSD sed and GNU sed).
# Uses cat to preserve original file's inode, permissions, and ownership.
sed_inplace() {
  local pattern="$1"
  local file="$2"
  local tmp
  tmp=$(mktemp)
  sed "$pattern" "$file" > "$tmp" && cat "$tmp" > "$file" && rm "$tmp"
}

# ---------------------------------------------------------------------------
# Backup and rollback
# ---------------------------------------------------------------------------

TIMESTAMP=$(date +%Y%m%d-%H%M%S)
TMPBACKUP_DIR=$(mktemp -d)
TMPBACKUP_FILES=""
CREATED_FILES=""

# Creates a persistent timestamped backup for manual rollback,
# and a temporary backup for auto-rollback on failure.
backup_file() {
  local src="$1"
  if [ -f "$src" ]; then
    local dir name backup_path
    dir=$(dirname "$src")
    name=$(basename "$src")
    backup_path="$dir/$name-backup-$TIMESTAMP"
    cp "$src" "$backup_path"
    echo "[Backup] Created $backup_path"
    cp "$src" "$TMPBACKUP_DIR/$name"
    TMPBACKUP_FILES="$TMPBACKUP_FILES $src"
  fi
}

# Track newly created files so rollback can remove them.
track_created_file() {
  local path="$1"
  CREATED_FILES="$CREATED_FILES $path"
}

rollback() {
  echo "[Error] setup.sh failed. Rolling back changes..." >&2
  for src in $TMPBACKUP_FILES; do
    local name
    name=$(basename "$src")
    if [ -f "$TMPBACKUP_DIR/$name" ]; then
      cp "$TMPBACKUP_DIR/$name" "$src"
      echo "[Rollback] Restored $src" >&2
    fi
  done
  for src in $CREATED_FILES; do
    if [ -f "$src" ]; then
      rm -f "$src"
      echo "[Rollback] Removed $src" >&2
    fi
  done
  rm -rf "$TMPBACKUP_DIR"
}

cleanup() {
  rm -rf "$TMPBACKUP_DIR"
}

trap rollback ERR
trap cleanup EXIT

# ---------------------------------------------------------------------------
# Check for existing tg_org_* token
# ---------------------------------------------------------------------------

TOKEN=""
for check_file in \
  "$HOME/.npmrc" \
  "$HOME/Library/Preferences/pnpm/rc" \
  "${XDG_CONFIG_HOME:-$HOME/.config}/pnpm/rc" \
  "$HOME/.yarnrc.yml" \
  "$HOME/.bunfig.toml" \
  "$HOME/.config/pip/pip.conf" \
  "$HOME/.config/uv/uv.toml" \
  "$HOME/.config/pypoetry/auth.toml" \
  "$HOME/Library/Application Support/pypoetry/auth.toml"; do
  if [ -z "$TOKEN" ] && [ -f "$check_file" ]; then
    TOKEN=$(grep -o 'tg_org_[A-Za-z0-9_-]*' "$check_file" 2>/dev/null | head -1 || true)
  fi
done

# ---------------------------------------------------------------------------
# Pre-check: ensure at least one configurable tool or config file exists
# ---------------------------------------------------------------------------
# Without this check, a clean environment (no tools, no config files) would
# mint a token on every run but never write it anywhere -- wasting tokens.

HAS_TARGET=false
if has_scope npm; then
  [ -f "$HOME/.npmrc" ] || command -v npm >/dev/null 2>&1 && HAS_TARGET=true
  command -v pnpm >/dev/null 2>&1 && HAS_TARGET=true
  [ -f "$HOME/.yarnrc.yml" ] && HAS_TARGET=true
  command -v yarn >/dev/null 2>&1 && HAS_TARGET=true
  [ -f "$HOME/.bunfig.toml" ] && HAS_TARGET=true
  command -v bun >/dev/null 2>&1 && HAS_TARGET=true
fi
if has_scope pypi; then
  [ -f "$HOME/.config/pip/pip.conf" ] && HAS_TARGET=true
  command -v pip3 >/dev/null 2>&1 && HAS_TARGET=true
  command -v pip >/dev/null 2>&1 && HAS_TARGET=true
  [ -f "$HOME/.config/uv/uv.toml" ] && HAS_TARGET=true
  command -v uv >/dev/null 2>&1 && HAS_TARGET=true
  command -v poetry >/dev/null 2>&1 && HAS_TARGET=true
fi

if [ "$HAS_TARGET" = false ]; then
  echo "[Done] No configurable tools found for scopes: $SCOPES. Skipping token mint."
  exit 0
fi

if [ -n "$TOKEN" ]; then
  echo "[Skip] Existing org token found, reusing"
  # Continue to configure scopes (don't exit -- allows incremental scope addition)
elif [ -n "${TG_PREMINTED_TOKEN:-}" ]; then
  TOKEN="$TG_PREMINTED_TOKEN"
  if ! printf '%s' "$TOKEN" | grep -qE '^tg_org_[A-Za-z0-9_-]{20,}$'; then
    echo "[Error] TG_PREMINTED_TOKEN format unexpected" >&2
    exit 1
  fi
  echo "[OK] Using pre-minted token"
else

# ---------------------------------------------------------------------------
# Mint a new org user token
# ---------------------------------------------------------------------------

API_URL="https://apiv2.cloud.shisho.dev/v1/guard/tokens/org-user"

HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$API_URL" \
  -H "Content-Type: application/json" \
  -d "{\"bot_id\":\"$BOT_ID\",\"api_key\":\"$API_KEY\",\"user_identifier\":\"$USER_IDENTIFIER\"}")

HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed '$d')
HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tail -1)

if ! [ "$HTTP_STATUS" -eq 201 ] 2>/dev/null; then
  echo "[Error] Token API returned HTTP $HTTP_STATUS" >&2
  echo "$HTTP_BODY" >&2
  exit 1
fi

# Extract token from JSON response
TOKEN=$(printf '%s' "$HTTP_BODY" | grep -oE '"token"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/"token"[[:space:]]*:[[:space:]]*"//;s/"$//')

if [ -z "$TOKEN" ]; then
  echo "[Error] Failed to extract token from API response" >&2
  exit 1
fi

# Validate token format (tg_org_ prefix + 43 Base64URL characters)
if ! printf '%s' "$TOKEN" | grep -qE '^tg_org_[A-Za-z0-9_-]{20,}$'; then
  echo "[Error] Token format unexpected" >&2
  exit 1
fi

echo "[OK] Token minted"

fi # end of mint-or-reuse block

# ---------------------------------------------------------------------------
# npm ecosystem
# ---------------------------------------------------------------------------

if has_scope npm; then

# --- npm / pnpm (.npmrc) ---

if [ -f "$HOME/.npmrc" ] || command -v npm >/dev/null 2>&1 || command -v pnpm >/dev/null 2>&1; then
  if grep -q 'tg_org_' "$HOME/.npmrc" 2>/dev/null; then
    echo "[OK] npm already configured"
  else
  if [ ! -f "$HOME/.npmrc" ]; then
    track_created_file "$HOME/.npmrc"
  fi
  backup_file "$HOME/.npmrc"

  if command -v npm >/dev/null 2>&1; then
    EXISTING_REGISTRY=$(npm config get registry --location=user 2>/dev/null || echo "")
    if [ -n "$EXISTING_REGISTRY" ] \
       && [ "$EXISTING_REGISTRY" != "https://npm.flatt.tech/" ] \
       && [ "$EXISTING_REGISTRY" != "https://registry.npmjs.org/" ] \
       && [ "$EXISTING_REGISTRY" != "undefined" ]; then
      echo "[WARN] Existing npm registry will be overwritten: $EXISTING_REGISTRY"
    fi

    npm config set "//npm.flatt.tech/:_authToken" "$TOKEN" --location=user
    npm config set registry "https://npm.flatt.tech/" --location=user
  else
    # npm not installed but .npmrc exists -- edit file directly
    if grep -qF "npm.flatt.tech" "$HOME/.npmrc" 2>/dev/null; then
      sed_inplace 's|//npm.flatt.tech/:_authToken=.*|//npm.flatt.tech/:_authToken='"$TOKEN"'|' "$HOME/.npmrc"
    else
      printf '//npm.flatt.tech/:_authToken=%s\nregistry=https://npm.flatt.tech/\n' "$TOKEN" >> "$HOME/.npmrc"
    fi
  fi
  echo "[OK] npm configured"
  fi # end tg_org_ check
else
  echo "[SKIP] npm not available"
fi

# --- pnpm (global rc) ---

pnpm_rc_dir=""
if [ -n "${XDG_CONFIG_HOME:-}" ]; then
  pnpm_rc_dir="$XDG_CONFIG_HOME/pnpm"
elif [ "$(uname)" = "Darwin" ]; then
  pnpm_rc_dir="$HOME/Library/Preferences/pnpm"
else
  pnpm_rc_dir="$HOME/.config/pnpm"
fi
pnpm_rc_path="$pnpm_rc_dir/rc"

if [ -f "$pnpm_rc_path" ]; then
  if grep -q 'tg_org_' "$pnpm_rc_path" 2>/dev/null; then
    echo "[OK] pnpm already configured"
  else
    backup_file "$pnpm_rc_path"
    if grep -qF "npm.flatt.tech" "$pnpm_rc_path" 2>/dev/null; then
      sed_inplace 's|//npm.flatt.tech/:_authToken=.*|//npm.flatt.tech/:_authToken='"$TOKEN"'|' "$pnpm_rc_path"
    else
      printf '//npm.flatt.tech/:_authToken=%s\nregistry=https://npm.flatt.tech/\n' "$TOKEN" >> "$pnpm_rc_path"
    fi
    echo "[OK] pnpm configured"
  fi
elif command -v pnpm >/dev/null 2>&1; then
  [ ! -d "$pnpm_rc_dir" ] && mkdir -p "$pnpm_rc_dir"
  printf '//npm.flatt.tech/:_authToken=%s\nregistry=https://npm.flatt.tech/\n' "$TOKEN" > "$pnpm_rc_path"
  track_created_file "$pnpm_rc_path"
  echo "[OK] pnpm configured"
else
  echo "[SKIP] pnpm not available"
fi

# --- yarn v2+ (.yarnrc.yml) ---

if [ -f "$HOME/.yarnrc.yml" ]; then
  if grep -q 'tg_org_' "$HOME/.yarnrc.yml" 2>/dev/null; then
    echo "[OK] yarn already configured"
  else
  backup_file "$HOME/.yarnrc.yml"

  if grep -qF "npm.flatt.tech" "$HOME/.yarnrc.yml" 2>/dev/null; then
    if grep -qF "npmAuthToken" "$HOME/.yarnrc.yml" 2>/dev/null; then
      sed_inplace 's|npmAuthToken:.*|npmAuthToken: "'"$TOKEN"'"|' "$HOME/.yarnrc.yml"
    else
      sed_inplace '/npmRegistryServer:/a\
npmAuthToken: "'"$TOKEN"'"' "$HOME/.yarnrc.yml"
    fi
  else
    if grep -qF "npmRegistryServer" "$HOME/.yarnrc.yml" 2>/dev/null; then
      sed_inplace 's|npmRegistryServer:.*|npmRegistryServer: "https://npm.flatt.tech/"|' "$HOME/.yarnrc.yml"
      sed_inplace '/npmRegistryServer:/a\
npmAuthToken: "'"$TOKEN"'"' "$HOME/.yarnrc.yml"
    else
      printf '\nnpmRegistryServer: "https://npm.flatt.tech/"\n' >> "$HOME/.yarnrc.yml"
      printf 'npmAuthToken: "%s"\n' "$TOKEN" >> "$HOME/.yarnrc.yml"
    fi
  fi
  echo "[OK] yarn configured"
  fi # end tg_org_ check
elif command -v yarn >/dev/null 2>&1; then
  printf 'npmRegistryServer: "https://npm.flatt.tech/"\n' > "$HOME/.yarnrc.yml"
  printf 'npmAuthToken: "%s"\n' "$TOKEN" >> "$HOME/.yarnrc.yml"
  track_created_file "$HOME/.yarnrc.yml"
  echo "[OK] yarn configured"
else
  echo "[SKIP] yarn not available"
fi

# --- bun (.bunfig.toml) ---

if [ -f "$HOME/.bunfig.toml" ]; then
  if grep -q 'tg_org_' "$HOME/.bunfig.toml" 2>/dev/null; then
    echo "[OK] bun already configured"
  else
  backup_file "$HOME/.bunfig.toml"

  if grep -qF "npm.flatt.tech" "$HOME/.bunfig.toml" 2>/dev/null; then
    sed_inplace '/npm\.flatt\.tech/s|token = "[^"]*"|token = "'"$TOKEN"'"|' "$HOME/.bunfig.toml"
  else
    if grep -q '^\[install\]' "$HOME/.bunfig.toml" 2>/dev/null; then
      sed_inplace '/^\[install\]/a\
registry = { url = "https://npm.flatt.tech/", token = "'"$TOKEN"'" }' "$HOME/.bunfig.toml"
    else
      printf '\n[install]\n' >> "$HOME/.bunfig.toml"
      printf 'registry = { url = "https://npm.flatt.tech/", token = "%s" }\n' "$TOKEN" >> "$HOME/.bunfig.toml"
    fi
  fi
  echo "[OK] bun configured"
  fi # end tg_org_ check
elif command -v bun >/dev/null 2>&1; then
  printf '[install]\nregistry = { url = "https://npm.flatt.tech/", token = "%s" }\n' "$TOKEN" > "$HOME/.bunfig.toml"
  track_created_file "$HOME/.bunfig.toml"
  echo "[OK] bun configured"
else
  echo "[SKIP] bun not available"
fi

fi # has_scope npm

# ---------------------------------------------------------------------------
# PyPI ecosystem
# ---------------------------------------------------------------------------

if has_scope pypi; then

# --- pip (~/.config/pip/pip.conf) ---

pip_conf_dir="$HOME/.config/pip"
pip_conf_path="$pip_conf_dir/pip.conf"

if [ -f "$pip_conf_path" ]; then
  if grep -q 'tg_org_' "$pip_conf_path" 2>/dev/null; then
    echo "[OK] pip already configured"
  else
  backup_file "$pip_conf_path"

  if grep -qF "pypi.flatt.tech" "$pip_conf_path" 2>/dev/null; then
    sed_inplace 's|index-url = .*pypi\.flatt\.tech.*|index-url = https://token:'"$TOKEN"'@pypi.flatt.tech/simple/|' "$pip_conf_path"
  else
    if grep -q '^\[global\]' "$pip_conf_path" 2>/dev/null; then
      sed_inplace '/^\[global\]/a\
index-url = https://token:'"$TOKEN"'@pypi.flatt.tech/simple/' "$pip_conf_path"
    else
      printf '\n[global]\nindex-url = https://token:%s@pypi.flatt.tech/simple/\n' "$TOKEN" >> "$pip_conf_path"
    fi
  fi
  echo "[OK] pip configured"
  fi # end tg_org_ check
elif command -v pip3 >/dev/null 2>&1 || command -v pip >/dev/null 2>&1; then
  [ ! -d "$pip_conf_dir" ] && mkdir -p "$pip_conf_dir"
  printf '[global]\nindex-url = https://token:%s@pypi.flatt.tech/simple/\n' "$TOKEN" > "$pip_conf_path"
  track_created_file "$pip_conf_path"
  echo "[OK] pip configured"
else
  echo "[SKIP] pip not available"
fi

# --- uv (~/.config/uv/uv.toml) ---

uv_toml_dir="$HOME/.config/uv"
uv_toml_path="$uv_toml_dir/uv.toml"

if [ -f "$uv_toml_path" ]; then
  if grep -q 'tg_org_' "$uv_toml_path" 2>/dev/null; then
    echo "[OK] uv already configured"
  else
  backup_file "$uv_toml_path"

  if grep -qF "pypi.flatt.tech" "$uv_toml_path" 2>/dev/null; then
    sed_inplace 's|url = ".*pypi\.flatt\.tech.*"|url = "https://token:'"$TOKEN"'@pypi.flatt.tech/simple/"|' "$uv_toml_path"
  else
    if grep -q 'default = true' "$uv_toml_path" 2>/dev/null; then
      sed_inplace 's|default = true|default = false|' "$uv_toml_path"
    fi
    printf '\n[[index]]\nurl = "https://token:%s@pypi.flatt.tech/simple/"\ndefault = true\n' "$TOKEN" >> "$uv_toml_path"
  fi
  echo "[OK] uv configured"
  fi # end tg_org_ check
elif command -v uv >/dev/null 2>&1; then
  [ ! -d "$uv_toml_dir" ] && mkdir -p "$uv_toml_dir"
  printf '[[index]]\nurl = "https://token:%s@pypi.flatt.tech/simple/"\ndefault = true\n' "$TOKEN" > "$uv_toml_path"
  track_created_file "$uv_toml_path"
  echo "[OK] uv configured"
else
  echo "[SKIP] uv not available"
fi

# --- poetry ---

if command -v poetry >/dev/null 2>&1; then
  # Poetry config path: macOS = ~/Library/Application Support/pypoetry/
  #                     Linux = ~/.config/pypoetry/
  poetry_auth=""
  for p in "$HOME/Library/Application Support/pypoetry/auth.toml" "$HOME/.config/pypoetry/auth.toml"; do
    [ -f "$p" ] && poetry_auth="$p" && break
  done
  if [ -n "$poetry_auth" ] && grep -q 'tg_org_' "$poetry_auth" 2>/dev/null; then
    echo "[OK] poetry already configured"
  else
    # Disable keyring to prevent interactive prompts and errors in non-GUI environments
    PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring poetry config repositories.takumi-guard https://pypi.flatt.tech/simple/ 2>/dev/null
    PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring poetry config http-basic.takumi-guard token "$TOKEN" 2>/dev/null
    echo "[OK] poetry configured"
  fi
else
  echo "[SKIP] poetry not available"
fi

fi # has_scope pypi

echo "[Done] Takumi Guard setup complete"
