#!/usr/bin/env bash
# ============================================================
# bootstrap.sh - thin installer for cpanel-admin-kit
# ============================================================
# Resolves a version, downloads the manifest and bundle, verifies
# the SHA256, installs the release side-by-side, flips the
# `current` symlink atomically, then execs the TUI.
#
# No business logic lives here. Every task lives in modules/.
#
# Usage:
#   curl -fsSL https://<host>/bootstrap.sh | bash
#   bash bootstrap.sh --version 0.1.0
#   bash bootstrap.sh --latest
#
# Environment:
#   CAK_BASE_URL       distribution root (default below)
#   CAK_BASE_DIR       install root (default /mjk/cpanelctl)
#   CAK_SKIP_EXEC=1    install only, do not start the TUI (tests)
#   CAK_TEST_MODE=1    allow file:// URLs and non-root installs (tests)
# ============================================================

# shellcheck shell=bash

set -u

CAK_PINNED_VERSION="0.1.0"
CAK_DEFAULT_BASE_URL="https://cpanelctl.mtitc.ir"
CAK_BASE_DIR="${CAK_BASE_DIR:-/mjk/cpanelctl}"
CAK_SKIP_EXEC="${CAK_SKIP_EXEC:-0}"
CAK_TEST_MODE="${CAK_TEST_MODE:-0}"

TMP_DIR=""

cleanup() {
    if [[ -n "${TMP_DIR}" && -d "${TMP_DIR}" ]]; then
        rm -rf "${TMP_DIR}" 2>/dev/null || true
    fi
}
trap cleanup EXIT

die() {
    printf 'ERROR: %s\n' "$*" >&2
    exit 1
}

info() {
    printf '[INFO] %s\n' "$*"
}

usage() {
    cat <<'EOF'
cpanel-admin-kit installer

  --version <x.y.z>   install an exact release (default: pinned release)
  --latest            install the newest published release
  --base-url <url>    override CAK_BASE_URL
  -h, --help          this help

The installer never imports a key and never changes server config.
It only places a verified release under the install prefix.
EOF
}

# ------------------------------------------------------------
# fetch <url> <output>
# ------------------------------------------------------------
fetch() {
    local _url="$1" _out="$2"

    case "${_url}" in
        file://*)
            [[ "${CAK_TEST_MODE}" == "1" ]] \
                || die "file:// URLs are only allowed with CAK_TEST_MODE=1"
            cp -- "${_url#file://}" "${_out}"
            ;;
        https://*)
            command -v curl >/dev/null 2>&1 || die "curl is required"
            curl -fsSL --proto '=https' --tlsv1.2 \
                --retry 2 --connect-timeout 15 \
                -o "${_out}" "${_url}"
            ;;
        *)
            die "refusing URL (https only): ${_url}"
            ;;
    esac
}

# ------------------------------------------------------------
# json_get <file> <key> - flat-manifest scalar reader, no jq.
# ------------------------------------------------------------
json_get() {
    local _file="$1" _key="$2" _value=""

    _value="$(sed -n 's/.*"'"${_key}"'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "${_file}" | head -n 1)"
    [[ -n "${_value}" ]] || return 1
    printf '%s' "${_value}"
}

# ------------------------------------------------------------
# Arguments
# ------------------------------------------------------------
TARGET_VERSION=""
USE_LATEST=0
BASE_URL="${CAK_BASE_URL:-${CAK_DEFAULT_BASE_URL}}"

while (( $# > 0 )); do
    case "$1" in
        --version)
            [[ $# -ge 2 ]] || die "--version needs a value"
            TARGET_VERSION="${2#v}"
            shift 2
            ;;
        --version=*)
            TARGET_VERSION="${1#*=}"
            TARGET_VERSION="${TARGET_VERSION#v}"
            shift
            ;;
        --latest)
            USE_LATEST=1
            shift
            ;;
        --base-url)
            [[ $# -ge 2 ]] || die "--base-url needs a value"
            BASE_URL="$2"
            shift 2
            ;;
        --base-url=*)
            BASE_URL="${1#*=}"
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            die "unknown option: $1 (try --help)"
            ;;
    esac
done

BASE_URL="${BASE_URL%/}"

# ------------------------------------------------------------
# Preflight
# ------------------------------------------------------------
if [[ "${CAK_TEST_MODE}" != "1" ]]; then
    [[ "$(id -u)" -eq 0 ]] || die "must run as root"
fi

[[ "${CAK_BASE_DIR}" == /* ]] || die "CAK_BASE_DIR must be an absolute path"

case "${BASE_URL}" in
    https://*) ;;
    file://*)
        [[ "${CAK_TEST_MODE}" == "1" ]] \
            || die "file:// URLs are only allowed with CAK_TEST_MODE=1"
        ;;
    http://*) die "plain http is not allowed; use https" ;;
    *) die "unsupported base URL: ${BASE_URL}" ;;
esac

command -v tar >/dev/null 2>&1 || die "tar is required"
command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required"

# ------------------------------------------------------------
# Resolve the manifest
# ------------------------------------------------------------
if (( USE_LATEST == 1 )); then
    MANIFEST_URL="${BASE_URL}/manifest.json"
else
    [[ -n "${TARGET_VERSION}" ]] || TARGET_VERSION="${CAK_PINNED_VERSION}"
    MANIFEST_URL="${BASE_URL}/releases/v${TARGET_VERSION}/manifest.json"
fi

mkdir -p "${CAK_BASE_DIR}" 2>/dev/null || die "cannot create ${CAK_BASE_DIR}"
TMP_DIR="$(mktemp -d "${CAK_BASE_DIR}/.install-tmp.XXXXXX")" \
    || die "cannot create a temporary directory under ${CAK_BASE_DIR}"

info "manifest: ${MANIFEST_URL}"
fetch "${MANIFEST_URL}" "${TMP_DIR}/manifest.json" \
    || die "cannot fetch the manifest"

VERSION="$(json_get "${TMP_DIR}/manifest.json" version)" \
    || die "manifest has no version"
SHA256="$(json_get "${TMP_DIR}/manifest.json" sha256)" \
    || die "manifest has no sha256"
BUNDLE_PATH="$(json_get "${TMP_DIR}/manifest.json" bundle_path)" \
    || die "manifest has no bundle_path"

[[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \
    || die "invalid version in manifest: ${VERSION}"
[[ "${SHA256}" =~ ^[0-9a-f]{64}$ ]] \
    || die "invalid sha256 in manifest"

if [[ -n "${TARGET_VERSION}" && "${VERSION}" != "${TARGET_VERSION}" ]]; then
    die "manifest version ${VERSION} does not match requested ${TARGET_VERSION}"
fi

BUNDLE_PATH="${BUNDLE_PATH#/}"
case "${BUNDLE_PATH}" in
    *..*) die "refusing bundle path with '..'" ;;
esac

# ------------------------------------------------------------
# Download and verify before touching the install prefix
# ------------------------------------------------------------
BUNDLE_URL="${BASE_URL}/${BUNDLE_PATH}"
info "version:  ${VERSION}"
info "bundle:   ${BUNDLE_URL}"
fetch "${BUNDLE_URL}" "${TMP_DIR}/bundle.tar.gz" \
    || die "cannot fetch the bundle"

ACTUAL_SHA="$(sha256sum "${TMP_DIR}/bundle.tar.gz" | awk '{print $1}')"
[[ "${ACTUAL_SHA}" == "${SHA256}" ]] \
    || die "SHA256 mismatch (want ${SHA256}, got ${ACTUAL_SHA})"
info "sha256:   verified"

# ------------------------------------------------------------
# Install side-by-side, then flip the symlink
# ------------------------------------------------------------
RELEASES_DIR="${CAK_BASE_DIR}/releases"
DEST="${RELEASES_DIR}/v${VERSION}"
CURRENT="${CAK_BASE_DIR}/current"

mkdir -p "${RELEASES_DIR}" || die "cannot create ${RELEASES_DIR}"

if [[ -d "${DEST}" ]]; then
    info "already installed: ${DEST}"
else
    STAGE="${RELEASES_DIR}/.install-v${VERSION}-$$"
    rm -rf "${STAGE}"
    mkdir -p "${STAGE}" || die "cannot create ${STAGE}"

    tar -xzf "${TMP_DIR}/bundle.tar.gz" -C "${STAGE}" \
        || die "cannot extract the bundle"

    [[ -f "${STAGE}/cpanel-admin-kit.sh" ]] \
        || die "bundle has no cpanel-admin-kit.sh"
    [[ -d "${STAGE}/lib" ]] || die "bundle has no lib/"
    [[ -f "${STAGE}/VERSION" ]] || die "bundle has no VERSION"

    BUNDLED_VERSION="$(tr -d '[:space:]' <"${STAGE}/VERSION")"
    [[ "${BUNDLED_VERSION}" == "${VERSION}" ]] \
        || die "bundle VERSION ${BUNDLED_VERSION} does not match ${VERSION}"

    # Ensure all shell scripts are executable (Windows-built bundles lose +x)
    find "${STAGE}" -type f -name '*.sh' -exec chmod +x {} + 2>/dev/null || true

    chmod -R go-w "${STAGE}" 2>/dev/null || true
    mv "${STAGE}" "${DEST}" || die "cannot move the release into place"
fi

LINK_TMP="${CAK_BASE_DIR}/.current.$$"
rm -f "${LINK_TMP}"
ln -sfn "${DEST}" "${LINK_TMP}" || die "cannot create the current symlink"
if ! mv -Tf "${LINK_TMP}" "${CURRENT}" 2>/dev/null; then
    rm -f "${CURRENT}"
    mv "${LINK_TMP}" "${CURRENT}" || die "cannot update ${CURRENT}"
fi

info "installed: ${DEST}"
info "current:   ${CURRENT}"

# exec replaces this shell, so the EXIT trap would not run. Clean the
# download directory explicitly before handing over to the TUI.
cleanup
TMP_DIR=""

# The TUI derives every runtime path from this value. Export it so an
# alternate install root (tests, staging) stays self-consistent.
export CAK_BASE_DIR

if [[ "${CAK_SKIP_EXEC}" == "1" ]]; then
    info "CAK_SKIP_EXEC=1: not starting the TUI"
    exit 0
fi

exec "${CURRENT}/cpanel-admin-kit.sh"
