#!/usr/bin/env bash
set -euo pipefail

# ─── Release script for partiri CLI ──────────────────────────────────────────
#
# Prerequisites:
#   - cargo & rustup
#   - cross (cargo install cross): for cross-compilation (requires Docker)
#   - jq: for Codeberg API responses
#   - CODEBERG_PUBLISH_TOKEN env var: personal access token with repo scope
#   - CARGO_PUBLISH_TOKEN env var: crates.io API token
#   - NPM_PUBLISH_TOKEN env var: npm automation token
#
# Usage:
#   ./scripts/release.sh          # builds, publishes, creates release
#   ./scripts/release.sh --dry-run # builds only, no publishing
# ─────────────────────────────────────────────────────────────────────────────

DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
  DRY_RUN=true
  echo "==> Dry run mode (no publishing)"
fi

# Read version from Cargo.toml
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
TAG="v${VERSION}"
echo "==> Releasing partiri-cli ${TAG}"

# ─── Checks ──────────────────────────────────────────────────────────────────

if ! $DRY_RUN; then
  if [[ -z "${CODEBERG_PUBLISH_TOKEN:-}" ]]; then
    echo "Error: CODEBERG_PUBLISH_TOKEN not set" >&2
    exit 1
  fi
  if [[ -z "${CARGO_PUBLISH_TOKEN:-}" ]]; then
    echo "Error: CARGO_PUBLISH_TOKEN not set" >&2
    exit 1
  fi
  if [[ -z "${NPM_PUBLISH_TOKEN:-}" ]]; then
    echo "Error: NPM_PUBLISH_TOKEN not set" >&2
    exit 1
  fi
fi

# Ensure working directory is clean
if [[ -n "$(git status --porcelain)" ]]; then
  echo "Error: working directory is not clean. Commit or stash changes first." >&2
  exit 1
fi

# ─── Test ─────────────────────────────────────────────────────────────────────

echo "==> Running tests"
cargo fmt --check
cargo clippy -- -D warnings
cargo test

# ─── Build ────────────────────────────────────────────────────────────────────

DIST_DIR="target/dist"
rm -rf "$DIST_DIR"
mkdir -p "$DIST_DIR"

# ── macOS SDK (needed by zigbuild to link Security/CoreFoundation frameworks) ──
MACOS_SDK="/opt/MacOSX11.3.sdk"
if [[ ! -d "$MACOS_SDK" ]]; then
  echo "==> Downloading macOS SDK"
  SDK_URL="https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz"
  curl -L "$SDK_URL" -o /tmp/MacOSX11.3.sdk.tar.xz
  sudo tar -xf /tmp/MacOSX11.3.sdk.tar.xz -C /opt/
  rm /tmp/MacOSX11.3.sdk.tar.xz
fi

# Targets: (target-triple, archive-name, builder)
# builder: "cargo" | "zigbuild" | "zigbuild-macos"
TARGETS=(
  "x86_64-unknown-linux-gnu:partiri-${TAG}-linux-x64:cargo"
  "aarch64-unknown-linux-gnu:partiri-${TAG}-linux-arm64:zigbuild"
  "x86_64-apple-darwin:partiri-${TAG}-darwin-x64:zigbuild-macos"
  "aarch64-apple-darwin:partiri-${TAG}-darwin-arm64:zigbuild-macos"
)

for entry in "${TARGETS[@]}"; do
  IFS=: read -r target archive builder <<< "$entry"
  echo "==> Building ${target}"

  if [[ "$builder" == "zigbuild-macos" ]]; then
    if ! command -v cargo-zigbuild &>/dev/null; then
      echo "Warning: 'cargo-zigbuild' not installed, skipping ${target}" >&2
      continue
    fi
    rustup target add "$target" 2>/dev/null || true
    SDKROOT="$MACOS_SDK" cargo zigbuild --release --target "$target"
  elif [[ "$builder" == "zigbuild" ]]; then
    if ! command -v cargo-zigbuild &>/dev/null; then
      echo "Warning: 'cargo-zigbuild' not installed, skipping ${target}" >&2
      continue
    fi
    rustup target add "$target" 2>/dev/null || true
    cargo zigbuild --release --target "$target"
  else
    rustup target add "$target" 2>/dev/null || true
    cargo build --release --target "$target"
  fi

  # Package
  BINARY="target/${target}/release/partiri"
  if [[ ! -f "$BINARY" ]]; then
    echo "Warning: binary not found at ${BINARY}, skipping" >&2
    continue
  fi

  # Strip if possible
  strip "$BINARY" 2>/dev/null || true

  tar czf "${DIST_DIR}/${archive}.tar.gz" -C "target/${target}/release" partiri
  sha256sum "${DIST_DIR}/${archive}.tar.gz" > "${DIST_DIR}/${archive}.tar.gz.sha256"
  echo "    -> ${archive}.tar.gz"
done

# Generate combined checksums
cat "${DIST_DIR}"/*.sha256 > "${DIST_DIR}/checksums-sha256.txt" 2>/dev/null || true

if $DRY_RUN; then
  echo ""
  echo "==> Dry run complete. Built archives in ${DIST_DIR}/"
  ls -lh "${DIST_DIR}/"
  exit 0
fi

# ─── Publish to crates.io ────────────────────────────────────────────────────

echo "==> Publishing to crates.io"
cargo publish --token "$CARGO_PUBLISH_TOKEN" || {
  if cargo search partiri-cli 2>/dev/null | grep -q "^partiri-cli = \"${VERSION}\""; then
    echo "    -> partiri-cli@${VERSION} already published, skipping"
  else
    echo "Error: cargo publish failed" >&2
    exit 1
  fi
}

# ─── Publish to npm ──────────────────────────────────────────────────────────

echo "==> Publishing npm packages"

# Authenticate npm with token
echo "//registry.npmjs.org/:_authToken=${NPM_PUBLISH_TOKEN}" > ~/.npmrc

# Map archive names to npm package directories
declare -A NPM_MAP=(
  ["partiri-${TAG}-linux-x64"]="cli-linux-x64"
  ["partiri-${TAG}-linux-arm64"]="cli-linux-arm64"
  ["partiri-${TAG}-darwin-x64"]="cli-darwin-x64"
  ["partiri-${TAG}-darwin-arm64"]="cli-darwin-arm64"
)

for archive in "${!NPM_MAP[@]}"; do
  pkg_dir="npm/${NPM_MAP[$archive]}"
  archive_file="${DIST_DIR}/${archive}.tar.gz"

  if [[ ! -f "$archive_file" ]]; then
    echo "    Skipping ${NPM_MAP[$archive]} (no archive)"
    continue
  fi

  # Extract binary into npm package
  tar xzf "$archive_file" -C "$pkg_dir/bin/"
  chmod +x "$pkg_dir/bin/partiri"

  # Update version and publish
  (cd "$pkg_dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version && npm publish --access public) || {
    echo "    -> @partiri/${NPM_MAP[$archive]}@${VERSION} already published, skipping"
  }
  echo "    -> @partiri/${NPM_MAP[$archive]}@${VERSION}"
done

# Publish umbrella package
(
  cd npm/partiri
  npm version "$VERSION" --no-git-tag-version --allow-same-version
  # Sync optionalDependencies versions
  node -e "
    const pkg = require('./package.json');
    for (const dep of Object.keys(pkg.optionalDependencies || {})) {
      pkg.optionalDependencies[dep] = '${VERSION}';
    }
    require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
  "
  npm publish --access public || true
)
echo "    -> @partiri/cli@${VERSION}"

# ─── Create Codeberg release ─────────────────────────────────────────────────

echo "==> Creating Codeberg release"

# Tag and push
git tag "$TAG"
git push origin master --tags

REPO="PartiriCloud/cli"
API="https://codeberg.org/api/v1"

# Create release
RELEASE_ID=$(curl -s -X POST "${API}/repos/${REPO}/releases" \
  -H "Authorization: token ${CODEBERG_PUBLISH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\", \"body\": \"Release ${TAG}\"}" \
  | python3 -c "import sys, json; print(json.load(sys.stdin)['id'])")

if [[ "$RELEASE_ID" == "null" || -z "$RELEASE_ID" ]]; then
  echo "Error: failed to create release" >&2
  exit 1
fi

# Upload assets
for file in "${DIST_DIR}"/*.tar.gz "${DIST_DIR}/checksums-sha256.txt"; do
  [[ -f "$file" ]] || continue
  filename=$(basename "$file")
  echo "    Uploading ${filename}"
  curl -s -X POST "${API}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${filename}" \
    -H "Authorization: token ${CODEBERG_PUBLISH_TOKEN}" \
    -H "Content-Type: application/octet-stream" \
    --data-binary "@${file}" > /dev/null
done

echo ""
echo "==> Done! Released ${TAG}"
echo "    Codeberg: https://codeberg.org/${REPO}/releases/tag/${TAG}"
echo "    crates.io: https://crates.io/crates/partiri-cli"
echo "    npm: https://www.npmjs.com/package/@partiri/cli"
