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

PRODUCT="AmigaRob's TVHeadend Butler"
MANIFEST_URL="https://repo.amigarob.com/butler/releases.json"
CHANNEL="${TVH_BUTLER_CHANNEL:-alpha}"
SETUP_ARGS=()
ASSUME_YES="${TVH_BUTLER_ASSUME_YES:-0}"

usage() {
  cat <<'USAGE'
AmigaRob's TVHeadend Butler installer

Usage:
  curl -fsSL https://repo.amigarob.com/butler/install.sh | sudo bash -s -- [options]

Profiles:
  --profile butler-only   Install Butler and core runtime packages only
  --profile vpn           Install Butler, core packages, OpenVPN, Nord public assets, and Butler FFmpeg
  --profile tvheadend     Install Butler, TVHeadend, core packages, and Butler FFmpeg
  --profile full-local    Install Butler, TVHeadend, VPN support, Kodi, and Butler FFmpeg
  --profile update        Update Butler without optional dependency work
  --profile repair        Alias of update for repair/reinstall wording

Useful generated-webpage options:
  --channel alpha|stable|development
  --install-tvheadend
  --tvheadend-official-repository
  --install-openvpn
  --download-nord-assets
  --install-kodi
  --no-custom-ffmpeg
  --rebuild-custom-ffmpeg
  --no-restart
  --countries gb,us,fr       Preselect enabled country playlists after install
  --exclude-countries xx      Preselect country exclusions after install
  --scan-selected-countries  Queue selected countries for scan-and-map when Butler starts
  --yes                     Do not prompt before running the selected install plan
USAGE
}

while (($#)); do
  case "$1" in
    --channel)
      CHANNEL="${2:-}"
      if [[ -z "$CHANNEL" ]]; then
        echo "--channel requires a value" >&2
        exit 2
      fi
      shift 2
      ;;
    --channel=*)
      CHANNEL="${1#*=}"
      shift
      ;;
    --install-butler)
      # Butler itself is always installed; keep this no-op for webpage-generated commands.
      shift
      ;;
    --yes|--assume-yes)
      ASSUME_YES=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      SETUP_ARGS+=("$1")
      shift
      ;;
  esac
done
if ((${#SETUP_ARGS[@]} == 0)); then
  SETUP_ARGS=(--profile vpn)
fi

CACHE_BUSTER="${TVH_BUTLER_MANIFEST_CACHE_BUSTER:-$(date +%s)}"
MANIFEST_FETCH_URL="${MANIFEST_URL}?v=${CACHE_BUSTER}"
MANIFEST_SIG_URL="${MANIFEST_URL}.sig?v=${CACHE_BUSTER}"
WORKDIR="$(mktemp -d /tmp/tvh-butler-bootstrap.XXXXXX)"
LOG_PATH="${TVH_BUTLER_SETUP_LOG:-/var/lib/tvh-butler/installer/setup.log}"
trap 'rm -rf "$WORKDIR"' EXIT

step() {
  local message="$1"
  printf '[Butler bootstrap] %s\n' "$message"
  mkdir -p "$(dirname "$LOG_PATH")" 2>/dev/null || true
  printf '[bootstrap] %s\n' "$message" >>"$LOG_PATH" 2>/dev/null || true
}

confirm() {
  if [[ "$ASSUME_YES" == "1" ]]; then
    return 0
  fi
  if [[ ! -r /dev/tty ]]; then
    echo "No interactive terminal is available for confirmation. Re-run with --yes if you intentionally want unattended installation." >&2
    exit 2
  fi
  printf '%s [y/N] ' "$1" >/dev/tty
  local answer
  read -r answer </dev/tty || answer=""
  case "${answer,,}" in
    y|yes) return 0 ;;
    *) echo "Installation cancelled." >&2; exit 1 ;;
  esac
}

if [[ ${EUID} -ne 0 ]]; then
  echo "Run this downloaded bootstrap with sudo." >&2
  exit 1
fi

step "Starting installer for channel '$CHANNEL' with options: ${SETUP_ARGS[*]}"
confirm "Proceed with these Butler installer options? This will install or update TVH-Butler. Optional system-package work only happens if selected by the profile or flags."
export DEBIAN_FRONTEND=noninteractive
missing=()
for command in python3 curl openssl tar; do
  command -v "$command" >/dev/null 2>&1 || missing+=("$command")
done
if ((${#missing[@]})); then
  step "Installing bootstrap prerequisites: ${missing[*]}"
  apt-get update
  apt-get install -y python3 curl ca-certificates openssl tar
fi

cat >"$WORKDIR/release-public-key.pem" <<'BUTLER_PUBLIC_KEY'
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAimyQUHQrxDDnLVUd5mi4ue6m4LXKPjOakbAsY1cXkEQ=
-----END PUBLIC KEY-----
BUTLER_PUBLIC_KEY

step "Downloading signed Butler release manifest"
curl --http1.1 --fail --silent --show-error --location --proto '=https' --tlsv1.2 \
  --output "$WORKDIR/releases.json" "$MANIFEST_FETCH_URL"
curl --http1.1 --fail --silent --show-error --location --proto '=https' --tlsv1.2 \
  --output "$WORKDIR/releases.json.sig" "$MANIFEST_SIG_URL"
step "Verifying release manifest signature"
openssl pkeyutl -verify -pubin -inkey "$WORKDIR/release-public-key.pem" \
  -sigfile "$WORKDIR/releases.json.sig" -rawin -in "$WORKDIR/releases.json" >/dev/null

python3 - "$WORKDIR/releases.json" "$CHANNEL" "$WORKDIR/release.env" <<'PY'
import json, shlex, sys
manifest = json.load(open(sys.argv[1], encoding='utf-8'))
if int(manifest.get('schema_version') or 0) != 1:
    raise SystemExit('Unsupported Butler release manifest')
row = (manifest.get('channels') or {}).get(sys.argv[2])
if not isinstance(row, dict):
    raise SystemExit('Requested Butler release channel is not published')
for key in ('url', 'sha256', 'version'):
    if not str(row.get(key) or '').strip():
        raise SystemExit(f'Missing release field: {key}')
with open(sys.argv[3], 'w', encoding='utf-8') as handle:
    for key in ('url', 'sha256', 'version'):
        handle.write(f"{key.upper()}={shlex.quote(str(row[key]))}\n")
PY
# The generated file contains only values from the verified signed manifest.
source "$WORKDIR/release.env"
if [[ "$URL" == *\?* ]]; then
  RELEASE_FETCH_URL="${URL}&v=${CACHE_BUSTER}"
else
  RELEASE_FETCH_URL="${URL}?v=${CACHE_BUSTER}"
fi
step "Downloading Butler release $VERSION"
curl --http1.1 --fail --silent --show-error --location --proto '=https' --tlsv1.2 \
  --output "$WORKDIR/release.tar.gz" "$RELEASE_FETCH_URL"
step "Verifying Butler release checksum"
if ! echo "$SHA256  $WORKDIR/release.tar.gz" | sha256sum --check --status; then
  ACTUAL_SHA256="$(sha256sum "$WORKDIR/release.tar.gz" | awk '{print $1}')"
  echo "Butler release checksum mismatch." >&2
  echo "Expected: $SHA256" >&2
  echo "Received: $ACTUAL_SHA256" >&2
  echo "The hosting cache may still contain an older copy; retrying with a new cache key should resolve it." >&2
  exit 1
fi
step "Butler release checksum verified"

mkdir "$WORKDIR/release"
python3 - "$WORKDIR/release.tar.gz" "$WORKDIR/release" <<'PY'
import pathlib, sys, tarfile
archive, destination = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2]).resolve()
with tarfile.open(archive, 'r:*') as bundle:
    members = bundle.getmembers()
    for member in members:
        name = member.name.replace('\\', '/')
        target = (destination / name).resolve()
        if member.issym() or member.islnk() or not (member.isfile() or member.isdir()):
            raise SystemExit(f'Unsafe release member: {member.name}')
        if name.startswith('/') or '..' in pathlib.Path(name).parts:
            raise SystemExit(f'Unsafe release path: {member.name}')
        if destination not in target.parents and target != destination:
            raise SystemExit(f'Release path escapes staging: {member.name}')
    bundle.extractall(destination, members=members)
PY
step "Extracting Butler release"
SOURCE_DIR="$(find "$WORKDIR/release" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
if [[ -z "$SOURCE_DIR" || ! -f "$SOURCE_DIR/tvh-butler-setup" ]]; then
  echo "The verified release does not contain tvh-butler-setup." >&2
  exit 1
fi
chmod +x "$SOURCE_DIR/tvh-butler-setup"
step "Handing over to tvh-butler-setup"
exec python3 "$SOURCE_DIR/tvh-butler-setup" install --source-dir "$SOURCE_DIR" "${SETUP_ARGS[@]}"
