#!/usr/bin/env bash
#
# ==============================================================================
# Script Name: install-terraria.sh
# Description: Ultra-optimized, production-ready installer and manager for Vanilla
#              or tModLoader Terraria servers on Ubuntu.
#
# Usage:       sudo bash install-terraria.sh [options]
#
# Non-interactive / automation:
#   Every prompt below can be skipped by passing a flag or setting the
#   matching environment variable (use `sudo -E` to preserve exported env
#   vars). If stdin is not a TTY and a value hasn't been supplied any other
#   way, the script falls back to the listed default automatically instead
#   of hanging.
#
# Options:
#   --unattended                  Never prompt; use flags/env vars/defaults for everything
#   --server-type=1|2             (TERRARIA_SERVER_TYPE)  default: 1  (1=Vanilla, 2=Modded/tModLoader)
#   --world-name=NAME             (TERRARIA_WORLD_NAME)   default: MyWorld
#   --port=PORT                   (TERRARIA_PORT)         default: 7777
#   --max-players=N               (TERRARIA_MAX_PLAYERS)  default: 8
#   --password=PASS               (TERRARIA_PASSWORD)     default: "" (none)
#   --motd=TEXT                   (TERRARIA_MOTD)         default: "Welcome!"
#   --difficulty=0-3              (TERRARIA_DIFFICULTY)   default: 0
#   --tunnel=y|n                  (TERRARIA_TUNNEL)       default: n
#   --firewall=y|n                (TERRARIA_FIREWALL)     default: y if ufw is active, else n
#   -h, --help                    Show this help and exit
#
# Examples:
#   sudo bash install-terraria.sh --unattended --port=7778 --difficulty=1
#   sudo TERRARIA_WORLD_NAME=Valhalla -E bash install-terraria.sh
# ==============================================================================

set -euo pipefail

# --- COLOR / OUTPUT HELPERS ---
# Colors only when writing to a real terminal that supports them; NO_COLOR
# (see https://no-color.org) and non-tty output (piping to a log file) fall
# back to plain text automatically.
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]] && command -v tput &>/dev/null && (( $(tput colors 2>/dev/null || echo 0) >= 8 )); then
    C_RESET=$(tput sgr0);  C_BOLD=$(tput bold)
    C_GREEN=$(tput setaf 2); C_YELLOW=$(tput setaf 3)
    C_RED=$(tput setaf 1);   C_CYAN=$(tput setaf 6)
else
    C_RESET=""; C_BOLD=""; C_GREEN=""; C_YELLOW=""; C_RED=""; C_CYAN=""
fi

TOTAL_STEPS=6
STEP=0
log_step()    { STEP=$((STEP + 1)); echo; echo "${C_BOLD}${C_CYAN}[${STEP}/${TOTAL_STEPS}] $*${C_RESET}"; }
log_info()    { echo "  ${C_CYAN}i${C_RESET} $*"; }
log_ok()      { echo "  ${C_GREEN}✓${C_RESET} $*"; }
log_warn()    { echo "  ${C_YELLOW}!${C_RESET} $*" >&2; }
log_err()     { echo "  ${C_RED}x${C_RESET} $*" >&2; }

# --- PRE-FLIGHT ROOT VALIDATION ---
[[ $EUID -eq 0 ]] || { log_err "Run as root (sudo bash $0)."; exit 1; }

# --- HELP TEXT ---
print_help() {
    cat <<'HELP'
Usage: sudo bash install-terraria.sh [options]

Every prompt can be skipped by passing a flag or setting the matching
environment variable (use `sudo -E` to preserve exported env vars). If
stdin is not a TTY and a value hasn't been supplied any other way, the
script falls back to the listed default automatically instead of hanging.

Options:
  --unattended                  Never prompt; use flags/env vars/defaults for everything
  --server-type=1|2             (TERRARIA_SERVER_TYPE)  default: 1  (1=Vanilla, 2=Modded/tModLoader)
  --world-name=NAME             (TERRARIA_WORLD_NAME)   default: MyWorld
  --port=PORT                   (TERRARIA_PORT)         default: 7777
  --max-players=N               (TERRARIA_MAX_PLAYERS)  default: 8
  --password=PASS               (TERRARIA_PASSWORD)     default: "" (none)
  --motd=TEXT                   (TERRARIA_MOTD)         default: "Welcome!"
  --difficulty=0-3              (TERRARIA_DIFFICULTY)   default: 0
  --tunnel=y|n                  (TERRARIA_TUNNEL)       default: n
  --firewall=y|n                (TERRARIA_FIREWALL)     default: y if ufw is active, else n
  -h, --help                    Show this help and exit

Examples:
  sudo bash install-terraria.sh --unattended --port=7778 --difficulty=1
  sudo TERRARIA_WORLD_NAME=Valhalla -E bash install-terraria.sh
HELP
}

# --- CONFIGURATION & PATH CONSTANTS ---
readonly USER="terraria"
readonly HOME_DIR="/home/${USER}"
readonly BASE_DIR="${HOME_DIR}/terraria"
readonly SERVICE="/etc/systemd/system/terraria.service"

# --- ARGUMENT PARSING ---
UNATTENDED=false
for arg in "$@"; do
    case "${arg}" in
        --unattended)       UNATTENDED=true ;;
        --server-type=*)    TERRARIA_SERVER_TYPE="${arg#*=}" ;;
        --world-name=*)     TERRARIA_WORLD_NAME="${arg#*=}" ;;
        --port=*)           TERRARIA_PORT="${arg#*=}" ;;
        --max-players=*)    TERRARIA_MAX_PLAYERS="${arg#*=}" ;;
        --password=*)       TERRARIA_PASSWORD="${arg#*=}" ;;
        --motd=*)           TERRARIA_MOTD="${arg#*=}" ;;
        --difficulty=*)     TERRARIA_DIFFICULTY="${arg#*=}" ;;
        --tunnel=*)         TERRARIA_TUNNEL="${arg#*=}" ;;
        --firewall=*)       TERRARIA_FIREWALL="${arg#*=}" ;;
        -h|--help)          print_help; exit 0 ;;
        *)                  log_err "Unknown option: ${arg}"; print_help; exit 1 ;;
    esac
done

# --- VALIDATORS ---
valid_servertype()  { [[ "$1" == "1" || "$1" == "2" ]]; }
valid_worldname()   { [[ -n "$1" && "$1" != */* ]]; }
valid_port()        { [[ "$1" =~ ^[0-9]+$ ]] && (( 10#$1 >= 1 && 10#$1 <= 65535 )); }
valid_maxplayers()  { [[ "$1" =~ ^[0-9]+$ ]] && (( 10#$1 >= 1 && 10#$1 <= 255 )); }
valid_difficulty()  { [[ "$1" =~ ^[0-3]$ ]]; }
valid_yn()          { [[ "${1,,}" =~ ^(y|yes|n|no)$ ]]; }

have_tty() { [[ -t 0 ]] && [[ -e /dev/tty ]]; }

# Resolve a setting from (in order): an existing flag/env var, an unattended
# or no-TTY fallback to the given default, or an interactive prompt.
# Usage: value=$(resolve_value VAR_NAME "Prompt text" "default" validator_fn)
resolve_value() {
    local preset_name=$1 prompt_text=$2 default_val=$3 validator=${4:-}
    local val

    if [[ -v "${preset_name}" ]]; then
        val="${!preset_name}"
    elif [[ "${UNATTENDED}" == "true" ]] || ! have_tty; then
        val="${default_val}"
    else
        while true; do
            read -rp "  ${C_BOLD}${prompt_text}${C_RESET} [${default_val}]: " val </dev/tty
            val="${val:-$default_val}"
            if [[ -z "${validator}" ]] || "${validator}" "${val}"; then
                break
            fi
            log_warn "Invalid value — try again."
        done
    fi

    if [[ -n "${validator}" ]] && ! "${validator}" "${val}"; then
        log_err "Invalid value for ${preset_name}: '${val}'"
        exit 1
    fi
    printf '%s' "${val}"
}

if ! have_tty && [[ "${UNATTENDED}" != "true" ]]; then
    log_info "No TTY detected — falling back to flags/env vars/defaults for any unanswered prompts."
    log_info "Pass --unattended (or the relevant flags) to silence this notice."
fi

# --- STEP 1: DEPENDENCY SETUP & USER CREATION ---
log_step "Preparing system environment"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq && apt-get install -yqq curl wget unzip jq
log_ok "Dependencies installed (curl, wget, unzip, jq)"

if id "${USER}" &>/dev/null; then
    log_ok "System user '${USER}' already exists"
else
    adduser --disabled-password --gecos "" "${USER}" &>/dev/null
    log_ok "Created system user '${USER}'"
fi

# --- STEP 2: SERVER TYPE SELECTION & RELEASE LOOKUP ---
log_step "Selecting server type & fetching release info"
if [[ ! -v TERRARIA_SERVER_TYPE && "${UNATTENDED}" != "true" ]] && have_tty; then
    echo "  1) Vanilla Terraria"
    echo "  2) tModLoader (Modded)"
fi
SERVER_TYPE=$(resolve_value TERRARIA_SERVER_TYPE "Choice" "1" valid_servertype)

if [[ "${SERVER_TYPE}" == "2" ]]; then
    IS_MODDED=true
    log_info "Fetching latest tModLoader release..."
    API_JSON=$(curl -sSL https://api.github.com/repos/tModLoader/tModLoader/releases/latest)
    VERSION=$(echo "${API_JSON}" | jq -r '.tag_name')
    [[ -n "${VERSION}" && "${VERSION}" != "null" ]] || { log_err "GitHub API limit or failure."; exit 1; }

    VERSION_DIR="${BASE_DIR}/tmodloader-${VERSION#v}"
    DOWNLOAD_URL=$(echo "${API_JSON}" | jq -r '.assets[] | select(.name == "tModLoader.zip") | .browser_download_url')
    SERVER_BINARY="${VERSION_DIR}/start-tModLoaderServer.sh"
else
    IS_MODDED=false
    log_info "Fetching latest Vanilla release..."
    ZIP_NAME=$(curl -sSL https://terraria.org/api/get/dedicated-servers-names | jq -r '.[0]')
    [[ -n "${ZIP_NAME}" && "${ZIP_NAME}" != "null" ]] || { log_err "Terraria API failure."; exit 1; }

    VERSION=$(echo "${ZIP_NAME}" | sed -E 's/terraria-server-([0-9a-zA-Z-]+)\.zip/\1/')
    VERSION_DIR="${BASE_DIR}/${VERSION}"
    DOWNLOAD_URL="https://terraria.org/api/download/pc-dedicated-server/${ZIP_NAME}"
    SERVER_BINARY="${VERSION_DIR}/TerrariaServer.bin.x86_64"
fi
log_ok "Target version: ${VERSION} ($( [[ "${IS_MODDED}" == "true" ]] && echo "modded" || echo "vanilla" ))"

WORLDS_DIR="${VERSION_DIR}/worlds"

# --- STEP 3: INSTALLATION & EXTRACTION ---
log_step "Installing server files"
if [[ -x "${SERVER_BINARY}" ]]; then
    log_ok "Version ${VERSION} is already installed — skipping download."
else
    log_info "Downloading and extracting release..."
    mkdir -p "${VERSION_DIR}"

    if [[ "${IS_MODDED}" == "true" ]]; then
        wget -q -O "${VERSION_DIR}/tModLoader.zip" "${DOWNLOAD_URL}"
        unzip -q -o "${VERSION_DIR}/tModLoader.zip" -d "${VERSION_DIR}"
        rm "${VERSION_DIR}/tModLoader.zip"
        chmod +x "${VERSION_DIR}"/start-tModLoaderServer* "${VERSION_DIR}"/TerrariaServer*.bin.* 2>/dev/null || true
    else
        wget -q -O "${BASE_DIR}/${ZIP_NAME}" "${DOWNLOAD_URL}"
        unzip -q -o "${BASE_DIR}/${ZIP_NAME}" -d "${BASE_DIR}"
        rm "${BASE_DIR}/${ZIP_NAME}"
        [[ -d "${VERSION_DIR}/Linux" ]] && mv "${VERSION_DIR}/Linux"/* "${VERSION_DIR}/" && rmdir "${VERSION_DIR}/Linux"
        chmod +x "${SERVER_BINARY}"
    fi

    # Ownership has to be fixed AFTER extraction, not before — unzip/wget run
    # as root and would otherwise leave every extracted file root-owned,
    # which breaks the terraria user's ability to chmod/exec its own files
    # (e.g. tModLoader's LaunchUtils/ScriptCaller.sh) once the service starts.
    chown -R "${USER}:${USER}" "${VERSION_DIR}"
    log_ok "Server files installed to ${VERSION_DIR}"
fi

install -d -m 0755 -o "${USER}" -g "${USER}" "${WORLDS_DIR}"

# --- WORLD MIGRATION ---
if ! compgen -G "${WORLDS_DIR}/*.wld" > /dev/null; then
    OLD_WLD=$(find "${BASE_DIR}" -path "*/worlds/*.wld" ! -path "${WORLDS_DIR}/*" 2>/dev/null | sort | tail -n1 || true)
    if [[ -n "${OLD_WLD}" ]]; then
        cp "${OLD_WLD}" "${WORLDS_DIR}/"
        log_ok "Migrated existing world file from a previous version."
    fi
fi

# --- STEP 4: CONFIGURATION SETUP ---
log_step "Configuring server"
CONFIG_FILE="${VERSION_DIR}/serverconfig.txt"
if [[ -f "${CONFIG_FILE}" ]]; then
    parse_cfg() { grep -E "^$1=" "${CONFIG_FILE}" | head -n1 | cut -d= -f2- | tr -d '\r' || echo "$2"; }
    D_NAME=$(parse_cfg "worldname" "MyWorld")
    D_PORT=$(parse_cfg "port" "7777")
    D_MAX=$(parse_cfg "maxplayers" "8")
    D_PASS=$(parse_cfg "password" "")
    D_MOTD=$(parse_cfg "motd" "Welcome!")
    D_DIFF=$(parse_cfg "difficulty" "0")

    # Snapshot the existing config + world before we overwrite anything below,
    # so a rerun/update can never silently orphan or clobber prior settings.
    BACKUP_DIR="${VERSION_DIR}/backups/$(date +%Y%m%d-%H%M%S)"
    mkdir -p "${BACKUP_DIR}"
    cp "${CONFIG_FILE}" "${BACKUP_DIR}/serverconfig.txt.bak"
    OLD_WORLD_PATH=$(parse_cfg "world" "")
    if [[ -n "${OLD_WORLD_PATH}" && -f "${OLD_WORLD_PATH}" ]]; then
        cp "${OLD_WORLD_PATH}" "${BACKUP_DIR}/"
    fi
    chown -R "${USER}:${USER}" "${VERSION_DIR}/backups"
    log_ok "Backed up existing config/world to ${BACKUP_DIR}"
else
    D_NAME="MyWorld"
    D_PORT="7777"
    D_MAX="8"
    D_PASS=""
    D_MOTD="Welcome!"
    D_DIFF="0"
fi

echo
W_NAME=$(resolve_value TERRARIA_WORLD_NAME "World Name" "${D_NAME}" valid_worldname)
W_PORT=$(resolve_value TERRARIA_PORT "Port" "${D_PORT}" valid_port)
W_MAX=$(resolve_value TERRARIA_MAX_PLAYERS "Max Players" "${D_MAX}" valid_maxplayers)
W_PASS=$(resolve_value TERRARIA_PASSWORD "Password (blank = none)" "${D_PASS}" "")
W_MOTD=$(resolve_value TERRARIA_MOTD "MOTD" "${D_MOTD}" "")
W_DIFF=$(resolve_value TERRARIA_DIFFICULTY "Difficulty (0=Normal, 1=Expert, 2=Master, 3=Journey)" "${D_DIFF}" valid_difficulty)

cat <<EOF > "${CONFIG_FILE}"
world=${WORLDS_DIR}/${W_NAME}.wld
autocreate=2
worldname=${W_NAME}
difficulty=${W_DIFF}
maxplayers=${W_MAX}
port=${W_PORT}
ip=0.0.0.0
password=${W_PASS}
motd=${W_MOTD}
worldpath=${WORLDS_DIR}
banlist=banlist.txt
secure=1
language=en-US
upnp=0
EOF

chown "${USER}:${USER}" "${CONFIG_FILE}"
log_ok "Wrote ${CONFIG_FILE}"

# --- STEP 5: FIREWALL & SYSTEMD SERVICE ---
log_step "Setting up firewall & systemd service"
if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then
    FW_DEFAULT="y"
else
    FW_DEFAULT="n"
fi
FW_ANSWER=$(resolve_value TERRARIA_FIREWALL "Open port ${W_PORT}/tcp in ufw?" "${FW_DEFAULT}" valid_yn)
if [[ "${FW_ANSWER,,}" =~ ^y ]]; then
    if command -v ufw &>/dev/null; then
        ufw allow "${W_PORT}"/tcp >/dev/null
        log_ok "Opened port ${W_PORT}/tcp via ufw."
    else
        log_warn "ufw not found; skipping firewall rule. Ensure port ${W_PORT}/tcp is reachable (router/cloud security group) manually."
    fi
else
    log_info "Skipped firewall rule — make sure port ${W_PORT}/tcp is reachable some other way."
fi

cat <<EOF > "${SERVICE}"
[Unit]
Description=Terraria Dedicated Server (${VERSION})
After=network.target

[Service]
User=${USER}
WorkingDirectory=${VERSION_DIR}
ExecStart=${SERVER_BINARY} -config ${CONFIG_FILE}
Restart=on-failure
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now terraria &>/dev/null
log_ok "systemd service 'terraria' enabled and started."

# --- STEP 6: READINESS CHECK ---
log_step "Starting server"
printf "  Waiting for startup "
STARTUP_OK=false
for ((i=0; i<30; i++)); do
    if journalctl -u terraria --since "1 minute ago" --no-pager | grep -qE "Initialization complete|Listening on port|Server started|New World"; then
        echo
        log_ok "Server online and listening on port ${W_PORT}."
        STARTUP_OK=true
        break
    fi
    sleep 2
    printf '%s.%s' "${C_CYAN}" "${C_RESET}"
done
echo

if [[ "${STARTUP_OK}" != "true" ]]; then
    log_warn "Server did not confirm startup within 60s."
    log_warn "This doesn't always mean it failed — large worlds can take longer to generate."
    log_warn "Check status : sudo systemctl status terraria"
    log_warn "Check logs   : sudo journalctl -u terraria -n 50 --no-pager"
fi

# --- OPTIONAL TUNNELING ---
TUNNEL_ANSWER=$(resolve_value TERRARIA_TUNNEL "Install playit.gg tunneling agent?" "n" valid_yn)
if [[ "${TUNNEL_ANSWER,,}" =~ ^y ]]; then
    curl -fsSL https://packages.playit.gg/install.sh | bash
    log_ok "playit.gg agent installed — run 'playit setup' to complete tunnel configuration."
fi

# --- DISCOVER NETWORK IP ADDRESSES ---
PUBLIC_IP=$(curl -s --max-time 3 https://api.ipify.org || echo "Unavailable")
LOCAL_IP=$(hostname -I | awk '{print $1}')

# --- SUMMARY ---
BOX="=============================================================="
echo
echo "${C_BOLD}${C_GREEN}${BOX}${C_RESET}"
echo "${C_BOLD}${C_GREEN}DEPLOYMENT COMPLETE${C_RESET}"
echo "  * Local IP    : ${LOCAL_IP:-127.0.0.1}:${W_PORT}"
echo "  * Public IP   : ${PUBLIC_IP}:${W_PORT}"
echo "  * Config File : ${CONFIG_FILE}"
echo "  * World Path  : ${WORLDS_DIR}/${W_NAME}.wld"
[[ "${IS_MODDED}" == "true" ]] && echo "  * Mods Folder : ${HOME_DIR}/.local/share/Terraria/tModLoader/Mods/"
[[ "${STARTUP_OK}" != "true" ]] && echo "  ${C_YELLOW}* Note        : startup not confirmed — see warnings above${C_RESET}"
echo "${C_BOLD}${C_GREEN}${BOX}${C_RESET}"
