#!/usr/bin/env bash
# Linux staging example. Never point these paths at a running installation.
set -Eeuo pipefail
umask 077
export LC_ALL=C

die() { printf '%s\n' "$*" >&2; exit 1; }
(( $# >= 5 )) || die 'Usage: prepare-update.sh PREP_ROOT STEAMCMD USER WORKSHOP_ROOT ID:@Folder [...]'
prep=$(realpath -e -- "$1")
steamcmd=$(realpath -e -- "$2")
account=$3
workshop=$(realpath -e -- "$4")
shift 4
[[ -d "$prep" && -w "$prep" && "$prep" != / ]] || die 'Use an existing private staging directory.'
[[ -f "$steamcmd" && -x "$steamcmd" && -d "$workshop" ]] || die 'Check the dedicated SteamCMD and Workshop paths.'
[[ "$account" =~ ^[A-Za-z0-9_]+$ && "$account" != YOUR_STEAM_LOGIN ]] || die 'Set your Steam login name, without a password.'
case "$workshop/" in "$prep/"*) die 'Keep the Workshop cache outside staging.';; esac
case "$prep/" in "$workshop/"*) die 'Keep staging outside the Workshop cache.';; esac
case "$steamcmd" in "$prep/"*) die 'Keep SteamCMD outside staging.';; esac

declare -A seen_ids=() seen_names=()
for entry in "$@"; do
    [[ "$entry" =~ ^([1-9][0-9]*):(@[A-Za-z0-9_-]+)$ ]] || die "Invalid mod mapping: $entry"
    id=${BASH_REMATCH[1]}; name=${BASH_REMATCH[2]}
    [[ ! -v seen_ids[$id] && ! -v seen_names[$name] ]] || die "Duplicate mod mapping: $entry"
    seen_ids[$id]=1; seen_names[$name]=1
done

# mkdir claims the single pending slot atomically. Failures leave it for review.
pending="$prep/pending"
mkdir -- "$pending" || die 'A pending set already exists, or staging is not writable. Inspect it before trying again.'
trap 'printf "Preparation stopped. Keep %s for inspection.\n" "$pending" >&2' ERR
mkdir -- "$pending/game" "$pending/mods" "$pending/logs"
printf '%s\n' "$@" > "$pending/mod-list.txt"

run_steam() {
    local log=$1
    shift
    # The dedicated SteamCMD uses its cached login. No password or Guard prompt.
    (
        cd -- "$(dirname -- "$steamcmd")"
        timeout --kill-after=30s 30m "$steamcmd" \
            +@ShutdownOnFailedCommand 1 +@NoPromptForPassword 1 \
            "$@" +quit </dev/null >"$log" 2>&1
    )
}

run_steam "$pending/logs/game.log" +force_install_dir "$pending/game" +login "$account" +app_update 223350
grep -Eq "Success! App '223350' (fully installed|already up to date)" "$pending/logs/game.log" || die 'Game success message missing; inspect game.log.'
[[ -s "$pending/game/DayZServer" ]] || die 'Linux DayZServer file missing from the staged game.'

for entry in "$@"; do
    id=${entry%%:*}; name=${entry#*:}
    run_steam "$pending/logs/$id.log" +login "$account" +workshop_download_item 221100 "$id"
    grep -Fq "Success. Downloaded item $id to \"$workshop/$id\"" "$pending/logs/$id.log" || die "Workshop success message or download path differs for $id."
    package=$(realpath -e -- "$workshop/$id")
    [[ "$package" == "$workshop/$id" && -d "$package" ]] || die "Unexpected package path for $id."
    # Reject links rather than copying references outside the downloaded package.
    find "$package" -type l -print > "$pending/logs/$id-links.txt"
    [[ ! -s "$pending/logs/$id-links.txt" ]] || die "Package $id contains symbolic links; inspect it manually."
    find "$package" -type f -print -quit > "$pending/logs/$id-files.txt"
    [[ -s "$pending/logs/$id-files.txt" ]] || die "Package $id is empty."
    cp -a -- "$package" "$pending/mods/$name"
done

(
    cd -- "$pending"
    find game mods -type f -print0 | sort -z | xargs -0 -r sha256sum -- > SHA256SUMS
    sha256sum --check --quiet SHA256SUMS
)
printf '%s\n' 'Downloads copied and hashed. Review logs, versions and mission changes before installation.' > "$pending/PREPARED.txt"
printf 'Prepared files: %s\n' "$pending"
