#!/bin/sh
set -eu

BASE_URL="${WHM_EMAILDESK_REPOSITORY_URL:-https://whmsender.emaildesk.bd}"
CHANNEL=stable
ALLOW_UNSIGNED_PILOT=false
ACTIVATE_ROUTING=false
INSTALL_MODE=auto
VERIFY_ONLY=false
PINNED_KEY_SHA256="${WHM_EMAILDESK_RELEASE_KEY_SHA256:-}"

usage() {
  cat <<'EOF'
Usage: install.sh [--channel stable|pilot] [--allow-unsigned-pilot]
                  [--fresh|--upgrade] [--activate-routing] [--verify-only]

Stable is the default and requires a signed release plus an independently
pinned release-key fingerprint. Pilot builds require explicit acknowledgement.
EOF
}

while [ "$#" -gt 0 ]; do
  case "$1" in
    --channel) [ "$#" -ge 2 ] || { usage >&2; exit 2; }; CHANNEL="$2"; shift 2 ;;
    --allow-unsigned-pilot) ALLOW_UNSIGNED_PILOT=true; shift ;;
    --activate-routing) ACTIVATE_ROUTING=true; shift ;;
    --verify-only) VERIFY_ONLY=true; shift ;;
    --fresh) INSTALL_MODE=fresh; shift ;;
    --upgrade) INSTALL_MODE=upgrade; shift ;;
    -h|--help) usage; exit 0 ;;
    *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
  esac
done

[ "$(id -u)" -eq 0 ] || { echo "Run this installer as root." >&2; exit 1; }
case "$CHANNEL" in stable|pilot) ;; *) echo "Unsupported channel: $CHANNEL" >&2; exit 2 ;; esac
for tool in curl python3 sha256sum tar; do command -v "$tool" >/dev/null 2>&1 || { echo "$tool is required" >&2; exit 1; }; done

workdir="$(mktemp -d /root/whm-emaildesk-download.XXXXXX)"
cleanup() { rm -rf -- "$workdir"; }
trap cleanup EXIT HUP INT TERM
umask 077

fetch() {
  url="$1" target="$2"
  curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location --max-redirs 3 --output "$target" "$url"
}

channel_url="$BASE_URL/releases/$CHANNEL"
fetch "$channel_url/latest.json" "$workdir/channel-latest.json" || {
  echo "No $CHANNEL release is currently published." >&2
  exit 1
}

metadata="$(python3 - "$workdir/channel-latest.json" "$CHANNEL" <<'PY'
import json,re,sys
path, expected = sys.argv[1:]
v=json.load(open(path,encoding="utf-8"))
safe=re.compile(r"^[A-Za-z0-9._-]+$")
if v.get("formatVersion") != 1 or v.get("product") != "WHM EmailDesk Sender": raise SystemExit("invalid channel pointer")
if v.get("channel") != expected: raise SystemExit("channel mismatch")
names=(v.get("packageVersion"),v.get("file"),v.get("releaseManifest"),v.get("checksums"))
if not all(isinstance(x,str) and safe.fullmatch(x) for x in names): raise SystemExit("unsafe release metadata")
if not isinstance(v.get("size"),int) or v["size"] <= 0: raise SystemExit("invalid archive size")
if not re.fullmatch(r"[a-f0-9]{64}",v.get("sha256", "")): raise SystemExit("invalid archive hash")
if not re.fullmatch(r"[a-f0-9]{64}",v.get("releaseManifestSha256", "")): raise SystemExit("invalid manifest hash")
print(*names, str(v["size"]), v["sha256"], v["releaseManifestSha256"], "true" if v.get("signed") else "false", sep="\n")
PY
)"
version="$(printf '%s\n' "$metadata" | sed -n '1p')"
archive="$(printf '%s\n' "$metadata" | sed -n '2p')"
manifest="$(printf '%s\n' "$metadata" | sed -n '3p')"
checksums="$(printf '%s\n' "$metadata" | sed -n '4p')"
expected_size="$(printf '%s\n' "$metadata" | sed -n '5p')"
expected_archive_sha="$(printf '%s\n' "$metadata" | sed -n '6p')"
expected_manifest_sha="$(printf '%s\n' "$metadata" | sed -n '7p')"
signed="$(printf '%s\n' "$metadata" | sed -n '8p')"

if [ "$CHANNEL" = stable ] && [ "$signed" != true ]; then echo "Refusing unsigned stable release." >&2; exit 1; fi
if [ "$signed" != true ] && { [ "$CHANNEL" != pilot ] || [ "$ALLOW_UNSIGNED_PILOT" != true ]; }; then
  echo "Unsigned pilot requires --channel pilot --allow-unsigned-pilot." >&2
  exit 1
fi

release_url="$channel_url/$version"
fetch "$release_url/latest.json" "$workdir/latest.json"
fetch "$release_url/$manifest" "$workdir/$manifest"
fetch "$release_url/$archive" "$workdir/$archive"
fetch "$release_url/$checksums" "$workdir/$checksums"

[ "$(sha256sum "$workdir/$manifest" | awk '{print $1}')" = "$expected_manifest_sha" ] || { echo "Manifest hash mismatch." >&2; exit 1; }
[ "$(wc -c < "$workdir/$archive" | tr -d ' ')" = "$expected_size" ] || { echo "Archive size mismatch." >&2; exit 1; }
[ "$(sha256sum "$workdir/$archive" | awk '{print $1}')" = "$expected_archive_sha" ] || { echo "Archive hash mismatch." >&2; exit 1; }

checksum_files="$(python3 - "$workdir/$checksums" <<'PY'
import re,sys
for line in open(sys.argv[1],encoding="utf-8"):
    match=re.fullmatch(r"[a-f0-9]{64}  ([A-Za-z0-9._-]+)\n?",line)
    if not match: raise SystemExit("invalid checksum inventory")
    print(match.group(1))
PY
)"
printf '%s\n' "$checksum_files" | while IFS= read -r name; do
  [ -f "$workdir/$name" ] || fetch "$release_url/$name" "$workdir/$name"
done

if [ "$signed" = true ]; then
  command -v openssl >/dev/null 2>&1 || { echo "openssl is required for signed releases" >&2; exit 1; }
  [ -n "$PINNED_KEY_SHA256" ] || { echo "Set WHM_EMAILDESK_RELEASE_KEY_SHA256 from the independently published fingerprint." >&2; exit 1; }
  signed_files="$(python3 - "$workdir/$manifest" <<'PY'
import json,re,sys
v=json.load(open(sys.argv[1],encoding="utf-8")); safe=re.compile(r"^[A-Za-z0-9._-]+$")
names=[v["artifact"]["signature"],v["signing"]["manifestSignature"],v["checksums"]["signature"],v["signing"]["latestSignature"],v["signing"]["publicKey"],v["verification"]["posix"]]
if not all(isinstance(x,str) and safe.fullmatch(x) for x in names): raise SystemExit("unsafe signed filename")
print(*names,sep="\n")
PY
)"
  printf '%s\n' "$signed_files" | while IFS= read -r name; do fetch "$release_url/$name" "$workdir/$name"; done
  verifier="$(printf '%s\n' "$signed_files" | sed -n '6p')"
  public_key="$(printf '%s\n' "$signed_files" | sed -n '5p')"
  (cd "$workdir" && sh "./$verifier" "./$manifest" "./$archive" "./$public_key" "$PINNED_KEY_SHA256")
else
  (cd "$workdir" && sha256sum -c "$checksums")
fi

extract="$workdir/extract"
mkdir -m 0700 "$extract"
tar -tzf "$workdir/$archive" > "$workdir/archive.list"
python3 - "$workdir/archive.list" <<'PY'
import posixpath,sys
for raw in open(sys.argv[1],encoding="utf-8",errors="strict"):
    name=raw.rstrip("\n")
    normalized=posixpath.normpath(name)
    if not name or name.startswith("/") or normalized == ".." or normalized.startswith("../"):
        raise SystemExit("unsafe archive member")
PY
tar -xzf "$workdir/$archive" -C "$extract" --strip-components=1
[ -x "$extract/install.sh" ] && [ -x "$extract/upgrade.sh" ] || { echo "Lifecycle entry points are missing." >&2; exit 1; }

if [ "$VERIFY_ONLY" = true ]; then
  echo "VERIFIED_VERSION=$version"
  echo "CHANNEL=$CHANNEL"
  exit 0
fi

if [ "$INSTALL_MODE" = auto ]; then
  if [ -e /opt/whm-emaildesk-sender/current ]; then INSTALL_MODE=upgrade; else INSTALL_MODE=fresh; fi
fi
if [ "$INSTALL_MODE" = fresh ]; then (cd "$extract" && ./install.sh --fresh); else (cd "$extract" && ./upgrade.sh); fi

if [ "$ACTIVATE_ROUTING" = true ]; then
  cli=/usr/local/cpanel/bin/whm-emaildesk-sender
  "$cli" health >/dev/null
  python3 - "$cli" <<'PY'
import json,subprocess,sys
v=json.loads(subprocess.check_output([sys.argv[1],"config","show"],text=True))
priority=v["priority_1_gateway"]; g=v["gateways"][priority]
if not (g.get("enabled") and g.get("passwordConfigured") and g.get("last_test_outcome")=="accepted"):
    raise SystemExit("Priority 1 gateway is not enabled, authenticated and connection-tested; routing was not activated")
PY
  "$cli" routing install
fi

echo "INSTALLED_VERSION=$version"
echo "CHANNEL=$CHANNEL"
echo "ROUTING_ACTIVATED=$ACTIVATE_ROUTING"
