#!/usr/bin/env bash
#
# WolfPanel Agent installer (v1 development).
#
# Usage:
#   curl -fsSL https://downloads.wolfpanel.net/agent/install.sh | sudo bash
#   curl -fsSL https://downloads.wolfpanel.net/agent/install.sh | sudo bash -s -- --token INSTALL_TOKEN
#   sudo ./install.sh --token dev-token            # local development
#
# v1 notes:
#   * For development the agent is installed from the local checkout (the src/
#     directory next to this script). Production downloads a versioned release
#     package from downloads.wolfpanel.net, verifies its SHA-256 against
#     latest.json, and only then extracts it.
#   * Discovery is read-only; the installer changes only WolfPanel's own dirs.
#   * Registration is best-effort: a registration failure leaves the service
#     installed and retrying, and the installer finishes with a warning.
#
set -euo pipefail

# --- Settings ---------------------------------------------------------------
# Base directories are overridable via env for testing; production defaults are
# unchanged so existing installations remain compatible.
OPT_DIR="${WOLFPANEL_OPT_DIR:-/opt/wolfpanel}"
ETC_DIR="${WOLFPANEL_ETC_DIR:-/etc/wolfpanel}"
VAR_DIR="${WOLFPANEL_VAR_DIR:-/var/lib/wolfpanel}"
LOG_DIR="${WOLFPANEL_LOG_DIR:-/var/log/wolfpanel}"
UNIT_DEST="${WOLFPANEL_SYSTEMD_UNIT:-/etc/systemd/system/wolfpanel-agent.service}"
VERSION_DIR=""  # resolved dynamically in main
RELEASE_BASE="${WOLFPANEL_RELEASE_BASE:-https://downloads.wolfpanel.net/agent}"
RELEASE_CHANNEL="${WOLFPANEL_RELEASE_CHANNEL:-stable}"

# True as soon as ANY base-directory env override is set, i.e. this is a
# sandboxed/test install. The real host's systemd is left alone in that case
# -- mirrors uninstall.sh's SANDBOXED guard so the two scripts agree on what
# "sandboxed" means and neither ever touches the real wolfpanel-agent.service
# for a run that redirected its file paths.
SANDBOXED=false
if [[ -n "${WOLFPANEL_OPT_DIR:-}${WOLFPANEL_ETC_DIR:-}${WOLFPANEL_VAR_DIR:-}${WOLFPANEL_LOG_DIR:-}" ]]; then
  SANDBOXED=true
fi

INSTALL_TOKEN=""
WOLFPANEL_API_URL="${WOLFPANEL_API_URL:-}"
ORIG_ENV_API_URL="${WOLFPANEL_API_URL}"
CLI_API_URL=""
FORCE_CONFIG=false
VERSION=""
RELEASE_DIR=""

# Resolved from latest.json on standalone installs.
MANIFEST_SHA256=""
MANIFEST_FILE=""
MANIFEST_SIGNATURE=""
MANIFEST_CHANNEL=""
MANIFEST_SIZE_BYTES=""
DOWNLOAD_URL=""

# --- Helpers ----------------------------------------------------------------
log()  { echo "[wolfpanel] $*"; }
warn() { echo "[wolfpanel][warning] $*" >&2; }
err()  { echo "[wolfpanel][error] $*" >&2; }
die()  { err "$*"; exit 1; }

parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --token) INSTALL_TOKEN="${2:-}"; shift 2 ;;
      --token=*) INSTALL_TOKEN="${1#*=}"; shift ;;
      --api-url) CLI_API_URL="${2:-}"; shift 2 ;;
      --api-url=*) CLI_API_URL="${1#*=}"; shift ;;
      --force-config) FORCE_CONFIG=true; shift ;;
      --channel|-c)
        RELEASE_CHANNEL="${2:-}"
        if [[ "$RELEASE_CHANNEL" != "stable" && "$RELEASE_CHANNEL" != "beta" && "$RELEASE_CHANNEL" != "dev" ]]; then
          die "invalid channel: ${RELEASE_CHANNEL}. Must be stable, beta, or dev."
        fi
        shift 2
        ;;
      --channel=*)
        RELEASE_CHANNEL="${1#*=}"
        if [[ "$RELEASE_CHANNEL" != "stable" && "$RELEASE_CHANNEL" != "beta" && "$RELEASE_CHANNEL" != "dev" ]]; then
          die "invalid channel: ${RELEASE_CHANNEL}. Must be stable, beta, or dev."
        fi
        shift
        ;;
      -c=*)
        RELEASE_CHANNEL="${1#*=}"
        if [[ "$RELEASE_CHANNEL" != "stable" && "$RELEASE_CHANNEL" != "beta" && "$RELEASE_CHANNEL" != "dev" ]]; then
          die "invalid channel: ${RELEASE_CHANNEL}. Must be stable, beta, or dev."
        fi
        shift
        ;;
      *) die "unknown argument: $1" ;;
    esac
  done
}

# Compute the SHA-256 of a file using whichever tool is available.
sha256_of() {
  local f="$1"
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$f" | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$f" | awk '{print $1}'
  elif command -v openssl >/dev/null 2>&1; then
    openssl dgst -sha256 "$f" | awk '{print $NF}'
  else
    return 1
  fi
}

# Extract a string field from a flat JSON manifest (no jq dependency).
#
# NOTE: under `set -o pipefail`, a grep with no match exits 1 and that status
# propagates through the rest of the pipe even though head/sed succeed --
# which would silently kill the whole script (set -e) the instant a manifest
# field is missing, before any of our `die "..."` checks ever run. The
# `|| true` neutralizes that so callers can check for an empty result and
# report a clear error instead of vanishing silently.
json_field() {
  local key="$1" file="$2"
  { grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$file" || true; } \
    | head -n1 | sed -E 's/.*:[[:space:]]*"([^"]*)"/\1/'
}

# Extract a numeric field from a flat JSON manifest (no jq dependency).
json_field_num() {
  local key="$1" file="$2"
  { grep -o "\"${key}\"[[:space:]]*:[[:space:]]*[0-9\"]*" "$file" || true; } \
    | head -n1 | sed -E 's/.*:[[:space:]]*"?([0-9]*)"?/\1/'
}

# Decode hex string to binary file using python or fallback commands.
decode_hex_to_bin() {
  local hex="$1" dest="$2"
  if command -v python3 >/dev/null 2>&1 && python3 --version >/dev/null 2>&1; then
    python3 -c 'import sys; sys.stdout.buffer.write(bytes.fromhex(sys.argv[1]))' "$hex" > "$dest"
  elif command -v python >/dev/null 2>&1 && python --version >/dev/null 2>&1; then
    python -c 'import sys; sys.stdout.buffer.write(bytes.fromhex(sys.argv[1]))' "$hex" > "$dest"
  elif command -v xxd >/dev/null 2>&1; then
    echo -n "$hex" | xxd -r -p > "$dest"
  elif command -v perl >/dev/null 2>&1; then
    perl -e 'print pack("H*", $ARGV[0])' "$hex" > "$dest"
  else
    return 1
  fi
}

# Download with a bounded number of retries so a single transient network
# blip doesn't fail the whole install; exhausting all attempts dies with a
# clear message instead of leaving a truncated/missing file for later steps
# to trip over silently.
download_to() {
  local url="$1" dest="$2"
  local max_attempts="${WOLFPANEL_DOWNLOAD_RETRIES:-3}"
  local attempt=1 delay=2

  command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 \
    || die "neither curl nor wget found; cannot download ${url}"

  while [[ "${attempt}" -le "${max_attempts}" ]]; do
    if command -v curl >/dev/null 2>&1; then
      if curl -fsSL --connect-timeout 15 --max-time 300 -o "$dest" "$url"; then
        return 0
      fi
    elif wget -q --timeout=300 -O "$dest" "$url"; then
      return 0
    fi
    rm -f "$dest"
    warn "download attempt ${attempt}/${max_attempts} failed for ${url}"
    attempt=$((attempt + 1))
    if [[ "${attempt}" -le "${max_attempts}" ]]; then
      sleep "${delay}"
      delay=$((delay * 2))
    fi
  done
  die "failed to download ${url} after ${max_attempts} attempts (network failure)"
}

resolve_api_url() {
  if [[ -n "${CLI_API_URL}" ]]; then
    WOLFPANEL_API_URL="${CLI_API_URL}"
  elif [[ -n "${ORIG_ENV_API_URL}" ]]; then
    WOLFPANEL_API_URL="${ORIG_ENV_API_URL}"
  elif [[ -f "${ETC_DIR}/agent.conf" ]] && [[ "${FORCE_CONFIG}" == "false" ]]; then
    # Extract from existing config
    local file_val
    file_val=$({ grep -E "^WOLFPANEL_API_URL=" "${ETC_DIR}/agent.conf" || true; } | head -n1 | cut -d'=' -f2- | tr -d '\047" ')
    if [[ -n "${file_val}" ]]; then
      WOLFPANEL_API_URL="${file_val}"
    else
      WOLFPANEL_API_URL="https://api.wolfpanel.net"
    fi
  else
    WOLFPANEL_API_URL="https://api.wolfpanel.net"
  fi
}

require_root() {
  [[ "$(id -u)" -eq 0 ]] || die "must run as root (use sudo)"
}

detect_platform() {
  OS="$(uname -s)"
  ARCH="$(uname -m)"
  log "detected platform: ${OS} ${ARCH}"
  # systemd is required, so an unsupported platform must hard-fail rather than
  # continue with only a warning.
  [[ "$OS" == "Linux" ]] || die "unsupported platform: ${OS}. WolfPanel Agent requires Linux with systemd."
}

create_dirs() {
  log "creating directory structure"
  mkdir -p "${VERSION_DIR}" "${ETC_DIR}/secrets" \
           "${VAR_DIR}/spool" "${VAR_DIR}/jobs" "${VAR_DIR}/discovery" "${VAR_DIR}/cache" \
           "${LOG_DIR}"
  chmod 0700 "${ETC_DIR}" || true
  chmod 0700 "${ETC_DIR}/secrets" || true
  chmod 0700 "${VAR_DIR}" || true
  chmod 0700 "${LOG_DIR}" || true
}

install_agent_files() {
  local script_dir
  script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"

  if [[ -d "${script_dir}/src" ]]; then
    # Development install from the local checkout.
    log "installing agent from local checkout"
    cp -r "${script_dir}/src" "${VERSION_DIR}/src"

    # Copy helper files that actually exist in the repository.
    for f in VERSION README.md config systemd deploy; do
      if [[ -e "${script_dir}/${f}" ]]; then
        cp -r "${script_dir}/${f}" "${VERSION_DIR}/"
      fi
    done
  else
    # Standalone install: download, VERIFY, then extract the release package.
    local temp_tar="${VAR_DIR}/cache/${MANIFEST_FILE}"

    mkdir -p "${VAR_DIR}/cache"
    log "downloading release package from ${DOWNLOAD_URL}"
    download_to "${DOWNLOAD_URL}" "${temp_tar}"

    # Size check first: catches a truncated/partial download early with a
    # clear message, before spending time on sha256/signature verification.
    local actual_size
    if command -v stat >/dev/null 2>&1 && stat -c%s "${temp_tar}" >/dev/null 2>&1; then
      actual_size="$(stat -c%s "${temp_tar}")"
    elif command -v stat >/dev/null 2>&1 && stat -f%z "${temp_tar}" >/dev/null 2>&1; then
      actual_size="$(stat -f%z "${temp_tar}")"
    else
      actual_size="$(wc -c < "${temp_tar}" | tr -d '[:space:]')"
    fi
    if [[ "${actual_size}" != "${MANIFEST_SIZE_BYTES}" ]]; then
      rm -f "${temp_tar}"
      die "size mismatch for ${MANIFEST_FILE}: expected ${MANIFEST_SIZE_BYTES} bytes, got ${actual_size}. Aborting before extraction."
    fi
    log "size verified: ${actual_size} bytes"

    # Integrity verification: refuse to extract an unverified tarball.
    local actual
    actual="$(sha256_of "${temp_tar}")" || die "no sha256 tool available to verify download"
    if [[ "${actual}" != "${MANIFEST_SHA256}" ]]; then
      rm -f "${temp_tar}"
      die "checksum mismatch for ${MANIFEST_FILE}: expected ${MANIFEST_SHA256}, got ${actual}. Aborting before extraction."
    fi
    log "checksum verified: ${actual}"

    # Cryptographic Signature Verification
    command -v openssl >/dev/null 2>&1 || die "openssl is required to verify release signatures"
    
    local pubkey_file
    pubkey_file="${VAR_DIR}/cache/release_pubkey.pem"
    cat > "${pubkey_file}" <<'PEMEOF'
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr34uui+vpx2AqxiC4zsC
ifojuxRTqR7JzxjwgKlrs1Mmp1AEm/DlfP5ZbGqJept5SHSW1RAMt2cXSLVKQSaA
qSG4K01rn/A4UHAIVxymDD+VFrc7yeGdLmcg1EfkNjoikxVdPJxkHPHLHoKneo+Y
V45yBBz0gImJ5ByTntfaXW5TNBKD9brEz/XMLDFFO5taA7364Nz6DgQFKFeg+fig
4xCO66+Bb2qtuo0NBSHrgQtpUHthdschOgwtF+oOY6+jsByPqR+qG1V1XNPM90wm
RmZ7W6ET5IX4Wlyn+8zb6CjrbOEhwlRWKadz4vhGh96wIuMtLyM2q/FX0Ntz//to
ZQIDAQAB
-----END PUBLIC KEY-----
PEMEOF

    local sig_file
    sig_file="${VAR_DIR}/cache/release_sig.bin"
    decode_hex_to_bin "${MANIFEST_SIGNATURE}" "${sig_file}" || {
      rm -f "${temp_tar}" "${pubkey_file}"
      die "failed to decode release signature hex"
    }

    if ! openssl dgst -sha256 -verify "${pubkey_file}" -signature "${sig_file}" "${temp_tar}" >/dev/null 2>&1; then
      rm -f "${temp_tar}" "${pubkey_file}" "${sig_file}"
      die "Signature verification failed: release package signature is invalid! Aborting before extraction."
    fi
    log "cryptographic signature verified successfully"
    rm -f "${pubkey_file}" "${sig_file}"

    log "extracting release package to ${VERSION_DIR}"
    tar -xzf "${temp_tar}" -C "${VERSION_DIR}"
    rm -f "${temp_tar}"

    # Cross-check: the extracted VERSION must match the manifest version, so we
    # never populate versions/<new> with old code (version/content mismatch).
    if [[ -f "${VERSION_DIR}/VERSION" ]]; then
      local extracted_version
      extracted_version="$(tr -d '\r\n[:space:]' < "${VERSION_DIR}/VERSION")"
      if [[ "${extracted_version}" != "${VERSION}" ]]; then
        die "version mismatch: manifest says ${VERSION} but extracted package is ${extracted_version}. Aborting."
      fi
      log "extracted VERSION matches manifest: ${extracted_version}"
    else
      die "extracted package is missing its VERSION file; aborting"
    fi
  fi

  # RELEASE_DIR is the resolved version directory.
  RELEASE_DIR="${VERSION_DIR}"

  # Verification checks
  log "verifying installed files in ${RELEASE_DIR}"
  [[ -f "${RELEASE_DIR}/src/main.py" ]] || die "Verification failed: ${RELEASE_DIR}/src/main.py is missing!"
  [[ -f "${RELEASE_DIR}/systemd/wolfpanel-agent.service" ]] || die "Verification failed: ${RELEASE_DIR}/systemd/wolfpanel-agent.service is missing!"
  [[ -f "${RELEASE_DIR}/VERSION" ]] || die "Verification failed: ${RELEASE_DIR}/VERSION is missing!"

  # Wrapper so systemd / operators call a stable path regardless of runtime.
  cat > "${VERSION_DIR}/wolfpanel-agent" <<'WRAP'
#!/usr/bin/env bash
exec python3 "$(dirname "$0")/src/main.py" "$@"
WRAP
  chmod +x "${VERSION_DIR}/wolfpanel-agent"

  # Atomic-swap-ready symlink: 'current' always points at the live version.
  ln -sfn "${VERSION_DIR}" "${OPT_DIR}/current"
  log "agent files installed at ${VERSION_DIR}"
}

write_config() {
  if [[ -f "${ETC_DIR}/agent.conf" ]] && [[ "${FORCE_CONFIG}" == "false" ]]; then
    log "config already exists; preserving it"
    # The installed VERSION file is the single source of truth for the running
    # build, so drop any stale WOLFPANEL_AGENT_VERSION left in an older config
    # to avoid reporting an outdated version on upgrade.
    if grep -qE "^WOLFPANEL_AGENT_VERSION=" "${ETC_DIR}/agent.conf"; then
      log "removing stale WOLFPANEL_AGENT_VERSION from existing config"
      grep -vE "^WOLFPANEL_AGENT_VERSION=" "${ETC_DIR}/agent.conf" > "${ETC_DIR}/agent.conf.tmp" \
        && mv "${ETC_DIR}/agent.conf.tmp" "${ETC_DIR}/agent.conf"
    fi
    # An existing install from before WOLFPANEL_RELEASE_BASE existed as a
    # config key has no line for it at all, so the running agent falls back
    # to its own in-code default. Backfill it so the agent and the installer
    # resolve the same manifest URL. A line that is already present (an
    # explicit user override, or one written by a prior run of this branch)
    # is left untouched -- this only ever adds the line, never rewrites it.
    if ! grep -qE "^WOLFPANEL_RELEASE_BASE=" "${ETC_DIR}/agent.conf"; then
      log "backfilling WOLFPANEL_RELEASE_BASE into existing config"
      printf 'WOLFPANEL_RELEASE_BASE=%s\n' "${RELEASE_BASE}" >> "${ETC_DIR}/agent.conf"
    fi
    chmod 0600 "${ETC_DIR}/agent.conf" || true
    return
  fi
  log "writing default config"
  # NOTE: only static configuration goes here (this file is also the systemd
  # EnvironmentFile). Runtime status/error/version live in state.json /
  # the VERSION file and must NOT be written here.
  cat > "${ETC_DIR}/agent.conf" <<EOF
WOLFPANEL_API_URL=${WOLFPANEL_API_URL}
WOLFPANEL_RELEASE_CHANNEL=${RELEASE_CHANNEL}
WOLFPANEL_RELEASE_BASE=${RELEASE_BASE}
WOLFPANEL_SERVER_ID=
WOLFPANEL_HEARTBEAT_INTERVAL=60
EOF
  chmod 0600 "${ETC_DIR}/agent.conf" || true
}

write_pending_state() {
  # Persist a clear pending state so the running service knows registration is
  # still owed and keeps retrying (without crash-looping).
  local reason="$1"
  mkdir -p "${VAR_DIR}"
  cat > "${VAR_DIR}/state.json" <<EOF
{
  "status": "pending_install",
  "last_error": "${reason}",
  "registration_pending": true,
  "revoked": false
}
EOF
}

register_agent() {
  log "Using Central API URL: ${WOLFPANEL_API_URL}"

  if [[ -z "${WOLFPANEL_API_URL}" ]] || [[ ! "${WOLFPANEL_API_URL}" =~ ^https?:// ]]; then
    warn "Invalid WOLFPANEL_API_URL: '${WOLFPANEL_API_URL}'. Skipping registration."
    write_pending_state "invalid api url"
    return 1
  fi

  if [[ -z "${INSTALL_TOKEN}" ]]; then
    # Pairing-flow placeholder (browser pairing not implemented in v1).
    cat <<EOF

No install token provided.

Browser pairing is not implemented in v1. Re-run with --token once you have an
install token to complete registration.

EOF
    log "skipping registration (no token)"
    write_pending_state "no install token provided"
    return 1
  fi

  log "registering agent with Central API"
  # The install token is passed through and used in-memory only; the agent
  # never writes it to disk. Registration is best-effort and must not abort the
  # whole install.
  if WOLFPANEL_API_URL="${WOLFPANEL_API_URL}" "${OPT_DIR}/current/wolfpanel-agent" register --token "${INSTALL_TOKEN}"; then
    log "registration succeeded"
    return 0
  fi

  warn "registration failed; the service will keep retrying. Re-run with --token to retry manually."
  write_pending_state "registration failed during install"
  return 1
}

install_service() {
  local unit_src
  unit_src="${RELEASE_DIR}/systemd/wolfpanel-agent.service"
  [[ -f "${unit_src}" ]] || die "service template not found at ${unit_src}"

  if [[ "${SANDBOXED}" == "true" ]]; then
    log "sandbox override active; skipping real systemd service management (unit template left at ${unit_src})"
    return
  fi

  # systemd is required for the agent to run.
  command -v systemctl >/dev/null 2>&1 || die "systemctl not found; systemd is required to run WolfPanel Agent"

  log "installing systemd service"
  cp "${unit_src}" "${UNIT_DEST}"
  systemctl daemon-reload
  systemctl enable wolfpanel-agent.service
  systemctl restart wolfpanel-agent.service
  log "service enabled and started"
}

resolve_version() {
  local script_dir
  script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"

  if [[ -d "${script_dir}/src" ]]; then
    # Local dev install: VERSION file is the source of truth.
    if [[ -f "${script_dir}/VERSION" ]]; then
      VERSION=$(tr -d '\r\n[:space:]' < "${script_dir}/VERSION")
    else
      VERSION="dev"
    fi
  else
    # Standalone install: fetch the manifest and read version + sha256 + file.
    RELEASE_BASE="${RELEASE_BASE%/}"
    local manifest_url="${RELEASE_BASE}/${RELEASE_CHANNEL}/latest.json"
    log "fetching latest version manifest from ${manifest_url}"
    mkdir -p "${VAR_DIR}/cache"
    local temp_manifest="${VAR_DIR}/cache/latest.json"
    download_to "${manifest_url}" "${temp_manifest}"

    [[ -s "${temp_manifest}" ]] || die "manifest at ${manifest_url} is empty or unreadable (bad JSON / network failure)"

    local manifest_version
    manifest_version="$(json_field_num manifest_version "${temp_manifest}")"
    if [[ -z "${manifest_version}" ]]; then
      rm -f "${temp_manifest}"
      die "malformed manifest: could not parse manifest_version from ${manifest_url} (invalid/corrupt JSON?)"
    fi
    [[ "${manifest_version}" == "1" ]] || die "unsupported manifest_version: ${manifest_version} (expected 1)"

    VERSION="$(json_field version "${temp_manifest}")"
    MANIFEST_FILE="$(json_field file "${temp_manifest}")"
    MANIFEST_SHA256="$(json_field sha256 "${temp_manifest}")"
    MANIFEST_SIGNATURE="$(json_field signature "${temp_manifest}")"
    MANIFEST_CHANNEL="$(json_field channel "${temp_manifest}")"
    MANIFEST_SIZE_BYTES="$(json_field_num size_bytes "${temp_manifest}")"
    rm -f "${temp_manifest}"

    [[ -n "${VERSION}" ]] || die "malformed manifest: missing required field 'version'"
    [[ -n "${MANIFEST_FILE}" ]] || die "malformed manifest: missing required field 'file'"
    [[ -n "${MANIFEST_SHA256}" ]] || die "malformed manifest: missing required field 'sha256'"
    [[ -n "${MANIFEST_SIGNATURE}" ]] || die "malformed manifest: missing required field 'signature'"
    [[ -n "${MANIFEST_CHANNEL}" ]] || die "malformed manifest: missing required field 'channel'"
    [[ -n "${MANIFEST_SIZE_BYTES}" ]] || die "malformed manifest: missing required field 'size_bytes'"

    # Defense in depth: refuse a manifest served under the wrong channel path
    # (e.g. the stable installer being handed a dev manifest by a
    # misconfigured or compromised downloads host).
    [[ "${MANIFEST_CHANNEL}" == "${RELEASE_CHANNEL}" ]] \
      || die "channel mismatch: requested channel '${RELEASE_CHANNEL}' but manifest reports channel '${MANIFEST_CHANNEL}'. Refusing to install."

    DOWNLOAD_URL="${RELEASE_BASE}/${RELEASE_CHANNEL}/${MANIFEST_FILE}"
  fi

  RELEASE_DIR="${OPT_DIR}/versions/${VERSION}"
  VERSION_DIR="${RELEASE_DIR}"
  log "resolved version: ${VERSION}"
}

main() {
  parse_args "$@"
  require_root
  detect_platform
  resolve_version
  resolve_api_url
  create_dirs
  install_agent_files
  write_config
  # Order per design: install + enable + start service first, THEN register.
  install_service
  local reg_ok=true
  register_agent || reg_ok=false

  if [[ "${reg_ok}" == "true" && "${SANDBOXED}" == "false" ]]; then
    # Reload the running service so it picks up freshly stored credentials.
    systemctl restart wolfpanel-agent.service || true
  fi

  cat <<EOF

WolfPanel Agent installation complete.
  version:  ${VERSION}
  binary:   ${OPT_DIR}/current/wolfpanel-agent
  config:   ${ETC_DIR}/agent.conf
  state:    ${VAR_DIR}/state.json
  logs:     ${LOG_DIR}/agent.log

Check status:  ${OPT_DIR}/current/wolfpanel-agent status
EOF

  if [[ "${reg_ok}" != "true" ]]; then
    warn "Installation finished, but registration is PENDING."
    warn "The service is running and will retry. Complete it with:"
    warn "  sudo ${OPT_DIR}/current/wolfpanel-agent register --token <INSTALL_TOKEN>"
  fi
}

# Run main only when executed directly; sourcing exposes functions for testing.
if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
  main "$@"
fi
