#!/usr/bin/env bash
# Cut a DashCaddy release: bump dashcaddy-api/package.json, commit, push,
# build tarball + version.json on DNS2 (the get.dashcaddy.net publishing
# host), refresh install.sh, then mirror everything to the dc-contabo-de
# get2 backup.
#
# Usage:   scripts/release.sh <version>
# Example: scripts/release.sh 1.4.0
#
# Pre-flight: must be on `main`, working tree clean, gitea remote reachable.
#
# Hosts/URLs are overridable via env:
#   DASHCADDY_RELEASE_HOST   default root@100.104.4.5      (DNS2, hosts get.dashcaddy.net)
#   DASHCADDY_MIRROR_HOST    default root@dc-contabo-de    (hosts get2.dashcaddy.net)
#   DASHCADDY_GITEA_URL      default http://100.98.123.59:3000/sami7777/dashcaddy.git

set -euo pipefail

VERSION="${1:-}"
[[ -z "$VERSION" ]] && { echo "Usage: $0 <version>" >&2; exit 1; }
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "Invalid version (need X.Y.Z): $VERSION" >&2; exit 1; }

REPO_ROOT="$(git rev-parse --show-toplevel)"
RELEASE_HOST="${DASHCADDY_RELEASE_HOST:-root@100.104.4.5}"
MIRROR_HOST="${DASHCADDY_MIRROR_HOST:-root@dc-contabo-de}"
GITEA_URL="${DASHCADDY_GITEA_URL:-http://100.98.123.59:3000/sami7777/dashcaddy.git}"

cd "$REPO_ROOT"

# ── Pre-flight ─────────────────────────────────────────────────────────────
[[ -f dashcaddy-api/package.json ]] || { echo "Run from dashcaddy repo root (no dashcaddy-api/package.json)" >&2; exit 1; }
[[ -n "$(git status --porcelain)" ]] && { echo "Working tree must be clean" >&2; exit 1; }
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
[[ "$BRANCH" == "main" ]] || { echo "Must be on main (current: $BRANCH)" >&2; exit 1; }

CURRENT="$(node -p "require('./dashcaddy-api/package.json').version")"
[[ "$CURRENT" == "$VERSION" ]] && { echo "package.json already at $VERSION — nothing to do" >&2; exit 1; }

echo "─── Cutting release ───"
echo "  current: $CURRENT"
echo "  target:  $VERSION"
echo "  release: $RELEASE_HOST"
echo "  mirror:  $MIRROR_HOST"
echo

# ── 1. Bump dashcaddy-api/package.json + root VERSION file ──────────────
echo "[1/6] Bumping dashcaddy-api/package.json + VERSION"
node -e "
  const fs = require('fs');
  const pkg = require('./dashcaddy-api/package.json');
  pkg.version = '$VERSION';
  fs.writeFileSync('./dashcaddy-api/package.json', JSON.stringify(pkg, null, 2) + '\n');
"
# Keep root VERSION in sync with package.json — otherwise downstream tooling
# (installer, status page, rollback checks) reads a stale version.
echo "$VERSION" > VERSION

# ── 2. Rebuild status frontend so dist/*.js matches source ────────────────
if [[ -f status/build.js ]]; then
  echo "[2/6] Rebuilding status frontend"
  (cd status && node build.js >/dev/null)
fi

# ── 3. Commit + push ──────────────────────────────────────────────────────
echo "[3/6] Committing + pushing"
git add dashcaddy-api/package.json VERSION
# Everything the build rewrites must be staged or the tarball ships stale
# copies. status/dist/ is .gitignored (-f bypasses); index.html and sw.js are
# tracked but get rewritten by build.js (CSP hash + SW cache tag derived from
# bundle content). Without staging sw.js, clients keep the old cache name
# and never see the new bundles.
[[ -d status/dist ]] && git add -f status/dist/ 2>/dev/null || true
[[ -f status/index.html ]] && git add status/index.html 2>/dev/null || true
[[ -f status/sw.js ]] && git add status/sw.js 2>/dev/null || true
git commit -m "chore(release): bump to $VERSION" >/dev/null
git push gitea main >/dev/null
COMMIT="$(git rev-parse --short HEAD)"
echo "  → committed: $COMMIT"

# ── 4. Build tarball on the publishing host ───────────────────────────────
echo "[4/6] Building tarball on $RELEASE_HOST"
ssh "$RELEASE_HOST" "set -e
  rm -rf /tmp/dashcaddy-build
  mkdir -p /tmp/dashcaddy-build
  cd /tmp/dashcaddy-build
  git clone --depth 1 '$GITEA_URL' dashcaddy >/dev/null 2>&1
  cd dashcaddy
  ACTUAL_COMMIT=\$(git rev-parse --short HEAD)
  if [ \"\$ACTUAL_COMMIT\" != \"$COMMIT\" ]; then
    echo \"  ! cloned commit \$ACTUAL_COMMIT does not match expected $COMMIT — aborting\" >&2
    exit 1
  fi
  rm -rf .git
  find . -type d -name node_modules -exec rm -rf {} + 2>/dev/null || true
  # Bake the actual commit SHA into the api VERSION file so containers built
  # from this tarball report a real commit (not the 'dev' placeholder).
  echo \"$COMMIT\" > dashcaddy-api/VERSION
  cd /tmp/dashcaddy-build
  tar zcf dashcaddy-$VERSION.tar.gz dashcaddy/
"
echo "  → built /tmp/dashcaddy-build/dashcaddy-$VERSION.tar.gz"

# ── 5. Publish on the release host ────────────────────────────────────────
echo "[5/6] Publishing v$VERSION + refreshed install.sh on $RELEASE_HOST"
ssh "$RELEASE_HOST" "set -e
  cd /var/www/get.dashcaddy.net
  cp -a release release.backup-\$(date -u +%Y%m%d-%H%M%S)
  cp /tmp/dashcaddy-build/dashcaddy-$VERSION.tar.gz release/
  cp /tmp/dashcaddy-build/dashcaddy-$VERSION.tar.gz release/latest.tar.gz
  ( cd release && sha256sum latest.tar.gz > latest.tar.gz.sha256 )
  cp /tmp/dashcaddy-build/dashcaddy/dashcaddy-installer/install.sh release/install.sh
  chmod +x release/install.sh
  SHA256=\$(sha256sum release/dashcaddy-$VERSION.tar.gz | cut -d' ' -f1)
  cat > release/version.json <<EOF
{
  \"version\": \"$VERSION\",
  \"commit\": \"$COMMIT\",
  \"date\": \"\$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
  \"sha256\": \"\$SHA256\",
  \"changelog\": \"$COMMIT chore(release): bump to $VERSION\",
  \"breaking\": false,
  \"tarball\": \"dashcaddy-$VERSION.tar.gz\"
}
EOF
"

# ── 6. Mirror to backup host (non-fatal — primary is canonical) ───────────
echo "[6/6] Mirroring to $MIRROR_HOST"
MIRROR_OK=true
if ssh "$RELEASE_HOST" "rsync -aq --delete /var/www/get.dashcaddy.net/release/ $MIRROR_HOST:/var/www/get2.dashcaddy.net/release/" 2>&1; then
  echo "  → mirrored"
else
  MIRROR_OK=false
  echo "  ! MIRROR FAILED — get2.dashcaddy.net is stale. Primary continues." >&2
fi

# ── Optional: notify known instances to update immediately ────────────────
# Set DASHCADDY_NOTIFY_TARGETS="<url>|<secret>,<url>|<secret>" to push.
# If unset, we try the co-located instance at localhost:3001 using the
# generated secret at /opt/dashcaddy/updates/notify-secret (silently skipped
# if either is missing).
if [[ -n "${DASHCADDY_NOTIFY_TARGETS:-}" ]]; then
  echo "[notify] pushing release to configured targets"
  IFS=',' read -ra TARGETS <<< "$DASHCADDY_NOTIFY_TARGETS"
  for t in "${TARGETS[@]}"; do
    url="${t%%|*}"
    secret="${t#*|}"
    [[ "$url" == "$secret" ]] && { echo "  ! malformed target ($t) — need url|secret" >&2; continue; }
    code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 -X POST \
      -H "X-DashCaddy-Notify-Secret: $secret" \
      -H 'Content-Type: application/json' \
      -d "{\"version\":\"$VERSION\",\"commit\":\"$COMMIT\"}" \
      "$url" || true)
    if [[ "$code" =~ ^2[0-9][0-9]$ ]]; then
      echo "  → $url notified (HTTP $code)"
    else
      echo "  ! $url notify FAILED (HTTP $code)" >&2
    fi
  done
else
  # Co-located default
  LOCAL_NOTIFY=$(ssh "$RELEASE_HOST" '
    if [[ -r /opt/dashcaddy/updates/notify-secret ]] && curl -fsS --max-time 2 http://localhost:3001/api/health >/dev/null 2>&1; then
      secret=$(cat /opt/dashcaddy/updates/notify-secret)
      curl -s -o /dev/null -w "%{http_code}" --max-time 5 -X POST \
        -H "X-DashCaddy-Notify-Secret: $secret" \
        -H "Content-Type: application/json" \
        -d "{\"version\":\"'"$VERSION"'\",\"commit\":\"'"$COMMIT"'\"}" \
        http://localhost:3001/api/system/update-notify
    else
      echo skip
    fi
  ' 2>/dev/null || true)
  case "$LOCAL_NOTIFY" in
    2*) echo "[notify] co-located instance on $RELEASE_HOST → HTTP $LOCAL_NOTIFY" ;;
    skip) ;;  # no secret or instance not up — silent
    *) echo "[notify] co-located instance notify failed (HTTP $LOCAL_NOTIFY)" >&2 ;;
  esac
fi

# ── Verify ───────────────────────────────────────────────────────────────
echo
echo "─── Verifying live ───"
SERVED_VER="$(curl -fsSL --max-time 5 https://get.dashcaddy.net/release/version.json | grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/')"
[[ "$SERVED_VER" == "$VERSION" ]] || { echo "MISMATCH: get.dashcaddy.net serves $SERVED_VER, expected $VERSION" >&2; exit 1; }
echo "  get.dashcaddy.net  → $SERVED_VER ✓"

SHA_LOCAL="$(ssh "$RELEASE_HOST" "sha256sum /var/www/get.dashcaddy.net/release/dashcaddy-$VERSION.tar.gz | cut -d' ' -f1")"
SHA_HTTP="$(curl -fsSL --max-time 30 "https://get.dashcaddy.net/release/dashcaddy-$VERSION.tar.gz" | sha256sum | cut -d' ' -f1)"
[[ "$SHA_LOCAL" == "$SHA_HTTP" ]] || { echo "SHA mismatch on served tarball" >&2; exit 1; }
echo "  tarball sha256     → $SHA_HTTP ✓"

if [[ "$MIRROR_OK" == "true" ]]; then
  GET2_VER="$(curl -fsSL --max-time 5 https://get2.dashcaddy.net/release/version.json 2>/dev/null | grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/')"
  [[ -z "$GET2_VER" ]] && GET2_VER=unreachable
  if [[ "$GET2_VER" == "$VERSION" ]]; then
    echo "  get2.dashcaddy.net → $GET2_VER ✓"
  else
    echo "  ! get2.dashcaddy.net serves '$GET2_VER' (expected $VERSION) — check Caddy/DNS for get2" >&2
  fi
fi

echo
echo "Done. v$VERSION published from commit $COMMIT."
[[ "$MIRROR_OK" == "true" ]] || echo "(reminder: mirror to get2 failed — investigate $MIRROR_HOST)"
