#!/usr/bin/env bash
# release-packages.sh — Version-bump + publish @bytelyst/* packages to Gitea npm registry
#
# Usage:
#   ./scripts/gitea/release-packages.sh              # apply pending changesets + publish missing/outdated packages
#   ./scripts/gitea/release-packages.sh --patch      # auto-bump all packages (patch) + publish
#   ./scripts/gitea/release-packages.sh --minor      # auto-bump all packages (minor) + publish
#   ./scripts/gitea/release-packages.sh --major      # auto-bump all packages (major) + publish
#   ./scripts/gitea/release-packages.sh --dry-run    # show what would be published, no side effects
#
# Required env:
#   GITEA_NPM_TOKEN — auth token for the Gitea npm registry (or ~/.gitea_npm_token file)
#
# Optional env:
#   GITEA_NPM_REGISTRY_URL — override auto-detected registry URL
#
# Network handling (automatic via NETWORK env var set by switch-network.sh):
#   NETWORK=corp  → localhost:3300 (SSH tunnel to Azure VM, proxy env stripped)
#   NETWORK=home  → Azure VM directly (gitea.bytelyst.com or ~/.gitea_vm_host)

set -euo pipefail

# ── Config ─────────────────────────────────────────────────────────────────────

REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"

# ── Network-aware Gitea resolution ─────────────────────────────────────────────
# Matches the pattern in publish-outdated-packages.sh
NETWORK_MODE="${NETWORK:-home}"

if [ "$NETWORK_MODE" = "corp" ]; then
  GITEA_HOST="${GITEA_NPM_HOST:-localhost}"
  GITEA_PORT="${GITEA_NPM_PORT:-3300}"
  GITEA_BASE="http://${GITEA_HOST}:${GITEA_PORT}"
  IS_CORP=true
else
  if [ -n "${GITEA_NPM_HOST:-}" ] && [ "${GITEA_NPM_HOST}" != "localhost" ]; then
    GITEA_HOST="$GITEA_NPM_HOST"
  elif [ -f "$HOME/.gitea_vm_host" ]; then
    GITEA_HOST="$(cat "$HOME/.gitea_vm_host")"
  else
    GITEA_HOST="gitea.bytelyst.com"
  fi
  GITEA_PORT="${GITEA_NPM_PORT:-3300}"
  GITEA_BASE="http://${GITEA_HOST}:${GITEA_PORT}"
  IS_CORP=false
fi

REGISTRY_URL="${GITEA_NPM_REGISTRY_URL:-${GITEA_BASE}/api/packages/learning_ai_user/npm/}"
AUTH_TARGET="${REGISTRY_URL#http://}"
AUTH_TARGET="${AUTH_TARGET#https://}"
TOKEN="${GITEA_NPM_TOKEN:-}"
TMP_DIR="${TMPDIR:-/tmp}/bytelyst-release-$$"
NPMRC_FILE="$TMP_DIR/.npmrc"  # single shared npmrc for all npm calls

# Workspace dirs that contain publishable npm packages
WORKSPACE_DIRS=("packages" "services" "dashboards")

# Native SDKs — not published to npm
SKIP_PACKAGES=("swift-platform-sdk" "swift-diagnostics" "kotlin-platform-sdk")

# Files/dirs to exclude from release commits
GIT_EXCLUDE_PATTERNS=("node-compile-cache" "*.tsbuildinfo")

# Parse flags
BUMP_TYPE=""
DRY_RUN=false
for arg in "$@"; do
  case "$arg" in
    --patch|--minor|--major) BUMP_TYPE="${arg#--}" ;;
    --dry-run) DRY_RUN=true ;;
    *) echo "Unknown argument: $arg"; exit 1 ;;
  esac
done

# ── Helpers ────────────────────────────────────────────────────────────────────

log()     { echo "▸ $*"; }
ok()      { echo "✅ $*"; }
warn()    { echo "⚠️  $*"; }
fail()    { echo "❌ $*" >&2; exit 1; }
info()    { echo "   $*"; }

pkg_field() {
  node -e "
    const fs = require('fs');
    const pkg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
    process.stdout.write(String(pkg['${1}'] ?? ''));
  " "$2"
}

is_skip_package() {
  local name="$1"
  for skip in "${SKIP_PACKAGES[@]}"; do
    [[ "$name" == *"$skip"* ]] && return 0
  done
  return 1
}

# Check registry using shared npmrc (reliable auth, proxy stripped)
version_on_registry() {
  local name="$1" version="$2"
  (cd "$TMP_DIR" && env \
    -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY \
    -u npm_config_proxy -u npm_config_https_proxy \
    -u NPM_CONFIG_PROXY -u NPM_CONFIG_HTTPS_PROXY \
    -u NPM_CONFIG_REGISTRY -u NPM_CONFIG_STRICT_SSL \
    -u NPM_CONFIG_NOPROXY -u NODE_TLS_REJECT_UNAUTHORIZED \
    npm view "${name}@${version}" version \
    --registry "$REGISTRY_URL" \
    --userconfig "$NPMRC_FILE" \
    --silent \
    2>/dev/null) || true
}

# ── Preflight ──────────────────────────────────────────────────────────────────

cd "$REPO_ROOT"

# Resolve token from file if not in env
if [ -z "$TOKEN" ] && [ -f "$HOME/.gitea_npm_token" ]; then
  TOKEN="$(cat "$HOME/.gitea_npm_token")"
fi
[ -z "$TOKEN" ] && fail "GITEA_NPM_TOKEN is not set (env var or ~/.gitea_npm_token)"
command -v pnpm >/dev/null 2>&1 || fail "pnpm not found in PATH"
command -v git  >/dev/null 2>&1 || fail "git not found in PATH"
command -v node >/dev/null 2>&1 || fail "node not found in PATH"

# Create shared tmp dir + npmrc early so version checks work before publishing
mkdir -p "$TMP_DIR"
trap 'rm -rf "$TMP_DIR"' EXIT
{
  printf '//%s:_authToken=%s\n' "$AUTH_TARGET" "$TOKEN"
  printf '@bytelyst:registry=%s\n' "$REGISTRY_URL"
  if [ "$IS_CORP" = true ]; then
    printf 'proxy=false\nhttps-proxy=false\n'
  fi
} > "$NPMRC_FILE"

# Show resolved config
log "Network: $NETWORK_MODE ($( [ "$IS_CORP" = true ] && echo "corp — localhost tunnel" || echo "home — Azure VM" ))"
info "Registry: $REGISTRY_URL"

# Verify token can read from registry
log "Verifying registry credentials..."
if ! (cd "$TMP_DIR" && env \
    -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY \
    -u npm_config_proxy -u npm_config_https_proxy \
    -u NPM_CONFIG_PROXY -u NPM_CONFIG_HTTPS_PROXY \
    -u NPM_CONFIG_REGISTRY -u NPM_CONFIG_STRICT_SSL \
    -u NPM_CONFIG_NOPROXY -u NODE_TLS_REJECT_UNAUTHORIZED \
    npm view "@bytelyst/errors" version \
    --registry "$REGISTRY_URL" \
    --userconfig "$NPMRC_FILE" \
    --silent 2>/dev/null); then
  fail "Registry auth failed — check GITEA_NPM_TOKEN has read+write:package scope"
fi
ok "Registry credentials verified"

[ "$DRY_RUN" = true ] && log "Dry-run mode — no packages will be published or committed"

# ── Phase 1: Pull-rebase from origin main ─────────────────────────────────────

log "Rebasing from origin main..."

STASHED=false
if ! git diff --quiet || ! git diff --cached --quiet; then
  log "Stashing local changes before rebase..."
  git stash push -u -m "release.sh: auto-stash before rebase"
  STASHED=true
fi

git fetch origin main
if ! git rebase origin/main; then
  [ "$STASHED" = true ] && git stash pop 2>/dev/null || true
  fail "Rebase failed — resolve conflicts and re-run"
fi

if [ "$STASHED" = true ]; then
  log "Restoring stashed changes..."
  if ! git stash pop; then
    echo ""
    warn "Stash pop has conflicts with the rebased code."
    info "Resolve the conflicts, then run:"
    info "  git add <resolved-files> && git stash drop"
    info "  Then re-run: ./scripts/release.sh"
    exit 1
  fi
fi

ok "Rebased to origin/main ($(git rev-parse --short HEAD))"

# ── Phase 2: Install dependencies ─────────────────────────────────────────────

log "Installing dependencies..."
pnpm install 2>&1 | grep -v "^Progress:" | grep -v "^packages/react-native" | grep -v "WARN  deprecated" || true
ok "Dependencies installed"

# ── Phase 3: Build all packages ───────────────────────────────────────────────

log "Building all packages..."
pnpm build 2>&1 | grep -E "Done|Error|error|failed|✓|✗" | grep -v "^packages.*build:" | head -20 || true
ok "Build complete"

# ── Phase 4: Apply changesets (if any) ────────────────────────────────────────

CHANGESET_FILES="$(find .changeset -maxdepth 1 -name '*.md' ! -name 'README.md' 2>/dev/null | wc -l | tr -d ' ')"

if [ -n "$BUMP_TYPE" ]; then
  log "Creating $BUMP_TYPE changeset for all publishable packages..."
  if [ "$DRY_RUN" = false ]; then
    BUMP_PKGS=()
    for ws_dir in "${WORKSPACE_DIRS[@]}"; do
      [ -d "$REPO_ROOT/$ws_dir" ] || continue
      while IFS= read -r -d '' pkg_json; do
        private="$(pkg_field private "$pkg_json")"
        [ "$private" = "true" ] && continue
        name="$(pkg_field name "$pkg_json")"
        is_skip_package "$name" && continue
        BUMP_PKGS+=("$name")
      done < <(find "$REPO_ROOT/$ws_dir" -mindepth 2 -maxdepth 2 -name package.json -print0 | sort -z)
    done

    CS_FILE=".changeset/release-$(date +%Y%m%d%H%M%S).md"
    {
      echo "---"
      for pkg in "${BUMP_PKGS[@]}"; do echo "\"$pkg\": $BUMP_TYPE"; done
      echo "---"
      echo ""
      echo "Release: automated $BUMP_TYPE version bump"
    } > "$CS_FILE"
    log "Created changeset: $CS_FILE (${#BUMP_PKGS[@]} packages)"
    CHANGESET_FILES="1"
  fi
fi

if [ "$CHANGESET_FILES" -gt 0 ]; then
  log "Applying $CHANGESET_FILES changeset(s)..."
  if [ "$DRY_RUN" = false ]; then
    pnpm changeset version
    ok "Version bumps applied"
    log "Rebuilding after version bumps..."
    pnpm build 2>&1 | grep -E "Done|Error|failed" | head -10 || true
    ok "Rebuild complete"
  else
    log "[dry-run] Would run: pnpm changeset version && pnpm build"
  fi
else
  log "No pending changesets — skipping version bump"
fi

# ── Phase 5: Detect and publish outdated/missing packages ─────────────────────

PUBLISHED=()
SKIPPED=()
ALREADY_PUBLISHED=()
FAILED=()

publish_package() {
  local pkg_dir="$1"
  local pkg_json="$pkg_dir/package.json"
  local name version private_flag

  name="$(pkg_field name "$pkg_json")"
  version="$(pkg_field version "$pkg_json")"
  private_flag="$(pkg_field private "$pkg_json")"

  # Skip private packages
  if [ "$private_flag" = "true" ]; then
    SKIPPED+=("$name@$version (private)")
    info "  ⊘ $name@$version (private)"
    return
  fi

  # Skip native SDKs
  if is_skip_package "$name"; then
    SKIPPED+=("$name@$version (native SDK)")
    info "  ⊘ $name@$version (native SDK)"
    return
  fi

  # Skip packages with no version
  if [ -z "$version" ]; then
    SKIPPED+=("$name (no version)")
    warn "  ⊘ $name — no version field"
    return
  fi

  # Check registry (uses shared npmrc — reliable auth)
  local remote_version
  remote_version="$(version_on_registry "$name" "$version")"

  if [ -n "$remote_version" ]; then
    ALREADY_PUBLISHED+=("$name@$version")
    info "  ✓ $name@$version (up-to-date)"
    return
  fi

  # Not on registry — needs publishing
  if [ "$DRY_RUN" = true ]; then
    log "  → [dry-run] would publish: $name@$version"
    PUBLISHED+=("$name@$version")
    return
  fi

  log "  → Publishing $name@$version..."

  local safe_name work_dir packed_tgz final_tgz
  safe_name="${name//@/}"
  safe_name="${safe_name//\//-}"
  work_dir="$TMP_DIR/$safe_name-$version"
  rm -rf "$work_dir"
  mkdir -p "$work_dir"

  # Pack with pnpm
  if ! (cd "$pkg_dir" && pnpm pack --pack-destination "$work_dir" 2>&1 | grep -v "^Wrote\|^packing"); then
    warn "  ✗ $name@$version — pack failed"
    FAILED+=("$name@$version (pack failed)")
    return
  fi

  packed_tgz="$(find "$work_dir" -maxdepth 1 -name '*.tgz' | head -1)"
  if [ -z "$packed_tgz" ]; then
    warn "  ✗ $name@$version — no tarball after pack"
    FAILED+=("$name@$version (pack failed)")
    return
  fi

  # Repack with npm for Gitea compatibility
  mkdir -p "$work_dir/unpacked"
  tar -xzf "$packed_tgz" -C "$work_dir/unpacked" 2>/dev/null
  if ! (cd "$work_dir/unpacked/package" && npm pack --pack-destination "$work_dir" 2>/dev/null); then
    warn "  ✗ $name@$version — repack failed"
    FAILED+=("$name@$version (repack failed)")
    return
  fi

  final_tgz="$(find "$work_dir" -maxdepth 1 -name '*.tgz' | sort | tail -1)"
  if [ -z "$final_tgz" ]; then
    warn "  ✗ $name@$version — no tarball after repack"
    FAILED+=("$name@$version (repack failed)")
    return
  fi

  # Publish using shared npmrc (strip proxy env so npm reaches localhost/VM directly)
  local publish_log="$work_dir/publish.log"
  local publish_ok=false
  if (cd "$TMP_DIR" && env \
      -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY \
      -u npm_config_proxy -u npm_config_https_proxy \
      -u NPM_CONFIG_PROXY -u NPM_CONFIG_HTTPS_PROXY \
      -u NPM_CONFIG_REGISTRY -u NPM_CONFIG_STRICT_SSL \
      -u NPM_CONFIG_NOPROXY -u NODE_TLS_REJECT_UNAUTHORIZED \
      npm publish "$final_tgz" \
      --registry "$REGISTRY_URL" \
      --userconfig "$NPMRC_FILE" \
      --silent 2>"$publish_log"); then
    publish_ok=true
  fi
  if [ "$publish_ok" = true ]; then
    ok "  ✅ $name@$version published"
    PUBLISHED+=("$name@$version")
  else
    local err
    err="$(cat "$publish_log" | grep -v '^npm notice' | head -3)"
    warn "  ✗ $name@$version — publish failed: $err"
    FAILED+=("$name@$version (publish error)")
  fi
}

log "Scanning workspace packages..."
echo ""
for ws_dir in "${WORKSPACE_DIRS[@]}"; do
  [ -d "$REPO_ROOT/$ws_dir" ] || continue
  log "[$ws_dir]"
  while IFS= read -r -d '' pkg_json; do
    publish_package "$(dirname "$pkg_json")"
  done < <(find "$REPO_ROOT/$ws_dir" -mindepth 2 -maxdepth 2 -name package.json -print0 | sort -z)
done
echo ""

# ── Phase 6: Commit and push ──────────────────────────────────────────────────

if [ "$DRY_RUN" = false ]; then
  # Exclude generated/cache dirs from commits
  for pattern in "${GIT_EXCLUDE_PATTERNS[@]}"; do
    git restore --staged "**/$pattern" 2>/dev/null || true
  done

  # Stage only tracked files + known release artifacts (not caches)
  git add \
    "packages/*/package.json" \
    "services/*/package.json" \
    "dashboards/*/package.json" \
    "package.json" \
    "pnpm-lock.yaml" \
    ".changeset/" \
    "CHANGELOG.md" 2>/dev/null || true

  if ! git diff --cached --quiet; then
    log "Committing version bumps..."
    git commit -m "chore: release version bumps [skip ci]"
    log "Pushing to origin main..."
    git push origin main
    ok "Pushed to origin/main"
  else
    log "No version bump changes to commit"
  fi
fi

# ── Summary ────────────────────────────────────────────────────────────────────

TOTAL_PKGS=$(( ${#PUBLISHED[@]} + ${#ALREADY_PUBLISHED[@]} + ${#SKIPPED[@]} + ${#FAILED[@]} ))

echo "════════════════════════════════════════"
echo "  Release Summary  (${TOTAL_PKGS} packages scanned)"
echo "════════════════════════════════════════"

if [ ${#PUBLISHED[@]} -gt 0 ]; then
  echo ""
  echo "Published (${#PUBLISHED[@]}):"
  for p in "${PUBLISHED[@]}"; do echo "  ✅ $p"; done
fi

if [ ${#FAILED[@]} -gt 0 ]; then
  echo ""
  echo "Failed (${#FAILED[@]}):"
  for p in "${FAILED[@]}"; do echo "  ❌ $p"; done
fi

if [ ${#ALREADY_PUBLISHED[@]} -gt 0 ]; then
  echo ""
  echo "Already up-to-date (${#ALREADY_PUBLISHED[@]}):"
  for p in "${ALREADY_PUBLISHED[@]}"; do echo "  ·  $p"; done
fi

if [ ${#SKIPPED[@]} -gt 0 ]; then
  echo ""
  echo "Skipped (${#SKIPPED[@]}):"
  for p in "${SKIPPED[@]}"; do echo "  ⊘  $p"; done
fi

echo ""
[ "$DRY_RUN" = true ] && echo "  (dry-run — no changes made)"

# Exit non-zero if any publishes failed
if [ ${#FAILED[@]} -gt 0 ]; then
  echo "  ⚠️  ${#FAILED[@]} package(s) failed to publish — see above"
  exit 1
fi

echo "════════════════════════════════════════"
