#!/usr/bin/env bash
# bootstrap-vm.sh — Idempotent Gitea bootstrap for a fresh (or existing) cloud VM.
#
# Goes from "VM running with Docker installed" → "Gitea running on :3300
# with admin + learning_ai_user accounts + npm-scoped token written to file".
#
# This script can run in two modes:
#
#   1. ON THE VM directly (recommended): SCP this file to the VM, SSH in, run.
#         scp scripts/gitea/bootstrap-vm.sh azureuser@<vm>:~/
#         ssh azureuser@<vm> 'bash bootstrap-vm.sh'
#
#   2. LOCALLY targeting a remote VM via SSH:
#         bash scripts/gitea/bootstrap-vm.sh --ssh-host azureuser@<vm>
#
# Usage:
#   bash bootstrap-vm.sh [OPTIONS]
#
# Options:
#   --admin-user NAME      Gitea admin username (default: gitea-admin)
#   --admin-email EMAIL    Gitea admin email (default: admin@bytelyst.local)
#   --admin-pass PASS      Gitea admin password (default: generated, printed)
#   --npm-user NAME        Owner for @bytelyst packages (default: learning_ai_user)
#   --npm-email EMAIL      Email for npm-user (default: npm@bytelyst.local)
#   --gitea-version VER    Gitea Docker tag (default: 1.22)
#   --data-dir PATH        Host path for Gitea data (default: /var/lib/gitea)
#   --output FILE          Write npm token to FILE (default: print to stdout
#                          with a fenced block for easy copy)
#   --ssh-host HOST        Run remotely via SSH (e.g. azureuser@10.0.0.4)
#   --skip-docker          Skip Docker install (assume present)
#   --dry-run              Print actions without executing
#   --force                Re-create admin/npm user even if they exist
#   -h, --help             Show this help
#
# Exit codes:
#   0  bootstrap succeeded
#   1  bootstrap failed at some step (error printed)
#   2  bad arguments
#
# Side effects (idempotent unless --force):
#   - Installs Docker (skipped if present, or with --skip-docker)
#   - Creates /var/lib/gitea (or --data-dir) — persistent across restarts
#   - Writes /etc/gitea/docker-compose.yml
#   - Starts/restarts `gitea` container on port 3300
#   - Creates admin user (skipped if exists)
#   - Creates npm-user via admin API (skipped if exists)
#   - Mints a new npm-scoped token (always — old token NOT revoked,
#     just supplemented; rotate via scripts/gitea/token.sh rotate)
#   - Writes token to --output FILE or stdout
#
# Re-runnable safely. Re-running:
#   - Detects existing Gitea, skips install
#   - Detects existing users, skips creation (unless --force)
#   - Always mints a fresh token (each run = new token; old ones still valid
#     unless you revoke them in Gitea UI)

set -uo pipefail

# ── Defaults ────────────────────────────────────────────────────
ADMIN_USER="gitea-admin"
ADMIN_EMAIL="admin@bytelyst.local"
ADMIN_PASS=""
NPM_USER="learning_ai_user"
NPM_EMAIL="npm@bytelyst.local"
GITEA_VERSION="1.22"
DATA_DIR="/var/lib/gitea"
OUTPUT_FILE=""
SSH_HOST=""
SKIP_DOCKER=false
DRY_RUN=false
FORCE=false
GITEA_PORT="3300"
GITEA_SSH_PORT="222"

while [ $# -gt 0 ]; do
  case "$1" in
    --admin-user)    ADMIN_USER="$2"; shift 2 ;;
    --admin-email)   ADMIN_EMAIL="$2"; shift 2 ;;
    --admin-pass)    ADMIN_PASS="$2"; shift 2 ;;
    --npm-user)      NPM_USER="$2"; shift 2 ;;
    --npm-email)     NPM_EMAIL="$2"; shift 2 ;;
    --gitea-version) GITEA_VERSION="$2"; shift 2 ;;
    --data-dir)      DATA_DIR="$2"; shift 2 ;;
    --output)        OUTPUT_FILE="$2"; shift 2 ;;
    --ssh-host)      SSH_HOST="$2"; shift 2 ;;
    --skip-docker)   SKIP_DOCKER=true; shift ;;
    --dry-run)       DRY_RUN=true; shift ;;
    --force)         FORCE=true; shift ;;
    -h|--help)       sed -n '2,42p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
    *) echo "Unknown arg: $1" >&2; exit 2 ;;
  esac
done

# ── Remote re-exec via SSH ──────────────────────────────────────
if [ -n "$SSH_HOST" ]; then
  echo "→ Re-executing on $SSH_HOST via SSH"
  # Copy this script to the remote and re-run with same args minus --ssh-host
  ARGS=()
  [ "$ADMIN_USER" != "gitea-admin" ]      && ARGS+=(--admin-user "$ADMIN_USER")
  [ "$ADMIN_EMAIL" != "admin@bytelyst.local" ] && ARGS+=(--admin-email "$ADMIN_EMAIL")
  [ -n "$ADMIN_PASS" ]                     && ARGS+=(--admin-pass "$ADMIN_PASS")
  [ "$NPM_USER" != "learning_ai_user" ]   && ARGS+=(--npm-user "$NPM_USER")
  [ "$NPM_EMAIL" != "npm@bytelyst.local" ] && ARGS+=(--npm-email "$NPM_EMAIL")
  [ "$GITEA_VERSION" != "1.22" ]          && ARGS+=(--gitea-version "$GITEA_VERSION")
  [ "$DATA_DIR" != "/var/lib/gitea" ]     && ARGS+=(--data-dir "$DATA_DIR")
  $SKIP_DOCKER && ARGS+=(--skip-docker)
  $DRY_RUN     && ARGS+=(--dry-run)
  $FORCE       && ARGS+=(--force)
  scp "$0" "$SSH_HOST:/tmp/bootstrap-vm.sh" >/dev/null
  ssh "$SSH_HOST" "bash /tmp/bootstrap-vm.sh ${ARGS[*]}"
  exit $?
fi

# ── Helpers ─────────────────────────────────────────────────────
say()  { echo "→ $1"; }
ok()   { echo "  ✓ $1"; }
err()  { echo "  ✗ $1" >&2; exit 1; }
run()  {
  if $DRY_RUN; then
    echo "    [dry-run] $*"
  else
    eval "$@"
  fi
}

# Generate random password if not set
gen_password() {
  # 24 chars, alphanumeric (safe for shell + URLs)
  if command -v openssl >/dev/null 2>&1; then
    openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 24
  else
    tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 24
  fi
}

if [ -z "$ADMIN_PASS" ]; then
  ADMIN_PASS="$(gen_password)"
  PASS_GENERATED=true
else
  PASS_GENERATED=false
fi

# Detect sudo
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
  SUDO="sudo"
fi

# ── Step 1: Install Docker ──────────────────────────────────────
step_docker() {
  say "Step 1/6: Docker"
  if $SKIP_DOCKER; then
    ok "Skipped (--skip-docker)"
    return
  fi
  if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
    ok "Docker + compose plugin already installed ($(docker --version))"
    return
  fi
  say "  Installing Docker via official convenience script…"
  run "curl -fsSL https://get.docker.com | $SUDO sh"
  run "$SUDO usermod -aG docker \"\$USER\" || true"
  ok "Docker installed (you may need to re-login for group membership)"
}

# ── Step 2: Write docker-compose.yml ────────────────────────────
step_compose() {
  say "Step 2/6: Gitea docker-compose"
  run "$SUDO mkdir -p $DATA_DIR /etc/gitea"
  local compose="/etc/gitea/docker-compose.yml"
  run "$SUDO tee $compose >/dev/null <<EOF
services:
  gitea:
    image: gitea/gitea:$GITEA_VERSION
    container_name: gitea
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__server__DOMAIN=\$(hostname)
      - GITEA__server__ROOT_URL=http://\$(hostname):$GITEA_PORT/
      - GITEA__server__HTTP_PORT=3000
      - GITEA__server__SSH_PORT=$GITEA_SSH_PORT
      - GITEA__service__DISABLE_REGISTRATION=true
      - GITEA__packages__ENABLED=true
    ports:
      - \"$GITEA_PORT:3000\"
      - \"$GITEA_SSH_PORT:22\"
    volumes:
      - $DATA_DIR:/data
EOF"
  ok "Wrote $compose"
}

# ── Step 3: Start Gitea + wait for ready ────────────────────────
step_start() {
  say "Step 3/6: Start Gitea container"
  run "cd /etc/gitea && $SUDO docker compose up -d"
  if $DRY_RUN; then
    ok "Would start gitea container"
    return
  fi
  say "  Waiting for Gitea HTTP on :$GITEA_PORT (up to 60s)…"
  local i=0
  while [ $i -lt 30 ]; do
    if curl -fsS "http://localhost:$GITEA_PORT/api/v1/version" >/dev/null 2>&1; then
      ok "Gitea reachable ($(curl -fsS "http://localhost:$GITEA_PORT/api/v1/version"))"
      return
    fi
    sleep 2
    i=$((i+1))
  done
  err "Gitea did not come up within 60s. Check: $SUDO docker logs gitea"
}

# ── Step 4: Create admin user via Gitea CLI ─────────────────────
step_admin() {
  say "Step 4/6: Admin user '$ADMIN_USER'"
  if $DRY_RUN; then
    echo "    [dry-run] $SUDO docker exec gitea gitea admin user create --admin ..."
    return
  fi
  # Check if admin already exists
  if $SUDO docker exec gitea gitea admin user list 2>/dev/null | awk 'NR>1 {print $2}' | grep -qx "$ADMIN_USER"; then
    if $FORCE; then
      say "  --force: re-setting admin password"
      $SUDO docker exec gitea gitea admin user change-password \
        --username "$ADMIN_USER" --password "$ADMIN_PASS" >/dev/null
      ok "Admin password reset"
    else
      ok "Admin '$ADMIN_USER' already exists — leaving password unchanged"
      PASS_GENERATED=false  # don't claim a fresh password if we didn't set one
    fi
  else
    $SUDO docker exec gitea gitea admin user create \
      --username "$ADMIN_USER" \
      --password "$ADMIN_PASS" \
      --email "$ADMIN_EMAIL" \
      --admin \
      --must-change-password=false >/dev/null
    ok "Admin '$ADMIN_USER' created"
  fi
}

# ── Step 5: Create npm-user via API ─────────────────────────────
step_npm_user() {
  say "Step 5/6: NPM owner '$NPM_USER'"
  if $DRY_RUN; then
    echo "    [dry-run] curl -u $ADMIN_USER:**** POST /api/v1/admin/users"
    return
  fi
  local base="http://localhost:$GITEA_PORT/api/v1"
  local code
  code=$(curl -fsS -o /tmp/gitea-user-check.json -w "%{http_code}" \
    -u "$ADMIN_USER:$ADMIN_PASS" "$base/users/$NPM_USER" 2>/dev/null || echo "000")
  if [ "$code" = "200" ] && ! $FORCE; then
    ok "NPM user '$NPM_USER' already exists"
  else
    local npm_pass; npm_pass="$(gen_password)"
    local resp
    resp=$(curl -fsS -u "$ADMIN_USER:$ADMIN_PASS" \
      -X POST "$base/admin/users" \
      -H 'Content-Type: application/json' \
      -d "{\"username\":\"$NPM_USER\",\"email\":\"$NPM_EMAIL\",\"password\":\"$npm_pass\",\"must_change_password\":false}" 2>&1)
    if echo "$resp" | grep -q '"id"'; then
      ok "NPM user '$NPM_USER' created"
    else
      err "Failed to create NPM user: $resp"
    fi
  fi
}

# ── Step 6: Mint npm token ──────────────────────────────────────
step_token() {
  say "Step 6/6: Mint npm token for '$NPM_USER'"
  if $DRY_RUN; then
    echo "    [dry-run] curl -u $NPM_USER POST /api/v1/users/$NPM_USER/tokens"
    NPM_TOKEN="DRY_RUN_TOKEN_PLACEHOLDER"
    return
  fi
  # We need the npm-user's password to call /tokens — but we only have it
  # at create time. Workaround: use admin Basic Auth to call the admin-only
  # `/admin/users/<u>/tokens` endpoint? Gitea does not expose this.
  #
  # Approach: temporarily reset npm-user's password via admin, mint via
  # the npm-user's own creds, then leave the password as-is (it's only used
  # for token minting; the npm flow uses the token).
  local npm_pass; npm_pass="$(gen_password)"
  $SUDO docker exec gitea gitea admin user change-password \
    --username "$NPM_USER" --password "$npm_pass" >/dev/null 2>&1 || true

  local token_name="npm-bootstrap-$(date +%Y%m%d-%H%M%S)"
  local base="http://localhost:$GITEA_PORT/api/v1"
  local resp
  resp=$(curl -fsS -u "$NPM_USER:$npm_pass" \
    -X POST "$base/users/$NPM_USER/tokens" \
    -H 'Content-Type: application/json' \
    -d "{\"name\":\"$token_name\",\"scopes\":[\"write:package\",\"read:package\"]}" 2>&1)
  NPM_TOKEN=$(echo "$resp" | grep -oE '"(sha1|token)":"[^"]+' | head -1 | sed 's/.*":"//')
  if [ -z "$NPM_TOKEN" ]; then
    err "Failed to mint token: $resp"
  fi
  ok "Token minted (name=$token_name, ${#NPM_TOKEN} chars)"
}

# ── Output token ────────────────────────────────────────────────
output_token() {
  if [ -n "$OUTPUT_FILE" ]; then
    if $DRY_RUN; then
      echo "[dry-run] would write token to $OUTPUT_FILE"
    else
      printf '%s' "$NPM_TOKEN" > "$OUTPUT_FILE"
      chmod 600 "$OUTPUT_FILE"
      ok "Token written to $OUTPUT_FILE (mode 600)"
    fi
  fi
}

# ── Final summary ───────────────────────────────────────────────
summary() {
  echo ""
  echo "════════════════════════════════════════════════════════════════"
  echo " ✅ Gitea bootstrap complete"
  echo "════════════════════════════════════════════════════════════════"
  echo ""
  echo "  Gitea URL:       http://$(hostname):$GITEA_PORT"
  echo "  Admin user:      $ADMIN_USER"
  if $PASS_GENERATED && [ -z "$OUTPUT_FILE" ]; then
    echo "  Admin password:  $ADMIN_PASS  ← SAVE THIS NOW (won't be shown again)"
  fi
  echo "  NPM owner:       $NPM_USER"
  echo "  Registry URL:    http://$(hostname):$GITEA_PORT/api/packages/$NPM_USER/npm/"
  echo ""
  if [ -z "$OUTPUT_FILE" ]; then
    echo "  ── NPM TOKEN (copy this NOW) ──"
    echo ""
    echo "  $NPM_TOKEN"
    echo ""
    echo "  Save to ~/.gitea_npm_token_home on your workstation:"
    echo ""
    echo "    echo -n '$NPM_TOKEN' > ~/.gitea_npm_token_home"
    echo "    chmod 600 ~/.gitea_npm_token_home"
    echo ""
  else
    echo "  NPM token:       written to $OUTPUT_FILE"
    echo ""
  fi
  echo "  Verify (from workstation):"
  echo ""
  echo "    GITEA_NPM_HOST=$(hostname) GITEA_NPM_OWNER=$NPM_USER \\"
  echo "    GITEA_NPM_TOKEN=\$(cat ~/.gitea_npm_token_home) \\"
  echo "    bash scripts/gitea/doctor.sh"
  echo ""
}

# ── Main ────────────────────────────────────────────────────────
say "Gitea VM bootstrap"
say "  Host:          $(hostname)"
say "  Admin user:    $ADMIN_USER"
say "  NPM owner:     $NPM_USER"
say "  Gitea version: $GITEA_VERSION"
say "  Data dir:      $DATA_DIR"
say "  Dry-run:       $DRY_RUN"
echo ""

step_docker
step_compose
step_start
step_admin
step_npm_user
step_token
output_token
summary
