#!/usr/bin/env bash
# =============================================================================
# onx-zip-create — Multi-source zip / tar.gz archive creation under home
#
# Purpose:
#   File Manager "Compress" backend. Accepts one or more sources (relative to
#   /home/<user>), produces an archive at the requested output path. Output
#   must also reside inside /home/<user>.
#
# Difference from onx-archive-create:
#   - Accepts a relative `output` path (vs. archive_name + same-dir)
#   - Per-source relative paths preserved inside the archive
#   - Supports password-protected zip when ZIP_PASSWORD is supplied
#
# Input (stdin JSON):
#   {
#     "username":  "onx_xxxx",
#     "sources":   ["public_html/index.html", "logs"],
#     "output":    "backups/snapshot.zip",         -- relative to home
#     "format":    "zip",                          -- zip | tar.gz
#     "password":  null,                           -- optional; zip-only
#     "level":     6                               -- optional; 0..9 compression
#   }
#
# Output (stdout JSON):
#   {
#     "archive_path": "/home/onx_xxxx/backups/snapshot.zip",
#     "format":       "zip",
#     "size_bytes":   25140,
#     "file_count":   42,
#     "sha256":       "..."
#   }
#
# Exit codes: 0=ok 1=invalid-input 2=preflight-fail 3=execution-fail
# Deployed to: /usr/local/onoxsoft/bin/onx-zip-create
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

require_cmd jq
require_cmd stat

onx_json_input

USERNAME="$(onx_json_field username)"
OUTPUT_REL="$(onx_json_field output)"
FORMAT="$(onx_json_field format zip)"
PASSWORD="$(onx_json_field password "")"
LEVEL="$(onx_json_field level 6)"

onx_validate_username "$USERNAME"
[[ -z "${OUTPUT_REL}" ]] && onx_die 1 "output is required"
[[ "${FORMAT}" =~ ^(zip|tar\.gz|tgz)$ ]] || onx_die 1 "format must be zip|tar.gz|tgz"
[[ "${LEVEL}" =~ ^[0-9]$ ]] || LEVEL=6

HOME_DIR="/home/${USERNAME}"
[[ -d "$HOME_DIR" ]] || onx_die 2 "home directory missing: ${HOME_DIR}"

# ── Validate sources (must be non-empty list, all under home) ──────────────
SOURCES_JSON="$(printf '%s' "$INPUT" | jq -c '.sources // []')"
mapfile -t SOURCES < <(printf '%s' "$SOURCES_JSON" | jq -r '.[]')
[[ ${#SOURCES[@]} -eq 0 ]] && onx_die 1 "sources is empty"

declare -a ABS_SOURCES=()
declare -a REL_SOURCES=()
for s in "${SOURCES[@]}"; do
    ABS="$(realpath -e "${HOME_DIR}/${s#/}" 2>/dev/null)" \
        || onx_die 1 "source not found: ${s}"
    case "${ABS}" in
        "${HOME_DIR}"|"${HOME_DIR}"/*) ;;
        *) onx_die 1 "source escapes /home/${USERNAME}: ${s}" ;;
    esac
    ABS_SOURCES+=("${ABS}")
    REL_SOURCES+=("${ABS#${HOME_DIR}/}")
done

# ── Validate output path ──────────────────────────────────────────────────
OUTPUT_INPUT="${HOME_DIR}/${OUTPUT_REL#/}"
OUTPUT_ABS="$(realpath -m "${OUTPUT_INPUT}" 2>/dev/null || printf '%s' "${OUTPUT_INPUT}")"
case "${OUTPUT_ABS}" in
    "${HOME_DIR}"|"${HOME_DIR}"/*) ;;
    *) onx_die 1 "output escapes /home/${USERNAME}: ${OUTPUT_REL}" ;;
esac

OUTPUT_DIR="$(dirname "${OUTPUT_ABS}")"
[[ -d "${OUTPUT_DIR}" ]] || mkdir -p "${OUTPUT_DIR}"

# Refuse silently clobbering an existing archive.
[[ -e "${OUTPUT_ABS}" ]] && onx_die 2 "output already exists: ${OUTPUT_REL}"

# ── Build archive ──────────────────────────────────────────────────────────
FILE_COUNT=0

if [[ "${FORMAT}" == "zip" ]]; then
    require_cmd zip

    ZIP_FLAGS=( -r -q )
    [[ -n "${PASSWORD}" ]] && ZIP_FLAGS+=( -P "${PASSWORD}" )
    ZIP_FLAGS+=( "-${LEVEL}" )

    # Run zip from inside HOME_DIR so entries are stored with home-relative
    # paths (cPanel parity: "public_html/index.html", not absolute).
    pushd "${HOME_DIR}" >/dev/null
    if ! zip "${ZIP_FLAGS[@]}" "${OUTPUT_ABS}" "${REL_SOURCES[@]}" 2>/dev/null; then
        popd >/dev/null
        rm -f "${OUTPUT_ABS}" 2>/dev/null || true
        onx_die 3 "zip failed"
    fi
    popd >/dev/null

    FILE_COUNT="$(unzip -l "${OUTPUT_ABS}" 2>/dev/null | tail -1 | awk '{print $2}' || echo 0)"
else
    require_cmd tar
    [[ -n "${PASSWORD}" ]] && onx_die 1 "password protection not supported for tar.gz"

    # GNU tar uses GZIP env var or -I to pass compression level.
    if ! GZIP="-${LEVEL}" tar -czf "${OUTPUT_ABS}" -C "${HOME_DIR}" "${REL_SOURCES[@]}" 2>/dev/null; then
        rm -f "${OUTPUT_ABS}" 2>/dev/null || true
        onx_die 3 "tar failed"
    fi
    FILE_COUNT="$(tar -tzf "${OUTPUT_ABS}" 2>/dev/null | wc -l || echo 0)"
fi

SIZE="$(stat -c '%s' "${OUTPUT_ABS}" 2>/dev/null || echo 0)"
SHA256="$(sha256sum "${OUTPUT_ABS}" 2>/dev/null | awk '{print $1}')"

# chown to home owner so the customer can manage the archive afterwards.
HOME_UID="$(stat -c '%u' "${HOME_DIR}" 2>/dev/null || echo "")"
HOME_GID="$(stat -c '%g' "${HOME_DIR}" 2>/dev/null || echo "")"
if [[ -n "${HOME_UID}" && -n "${HOME_GID}" ]]; then
    chown "${HOME_UID}:${HOME_GID}" "${OUTPUT_ABS}" 2>/dev/null || true
fi
chmod 0644 "${OUTPUT_ABS}" 2>/dev/null || true

onx_log "zip-create: user=${USERNAME} archive='${OUTPUT_REL}' format=${FORMAT} size=${SIZE} files=${FILE_COUNT}"

jq -nc \
    --arg archive_path "${OUTPUT_ABS}" \
    --arg format "${FORMAT}" \
    --argjson size_bytes "${SIZE}" \
    --argjson file_count "${FILE_COUNT:-0}" \
    --arg sha256 "${SHA256}" \
    '{
        archive_path: $archive_path,
        format: $format,
        size_bytes: $size_bytes,
        file_count: $file_count,
        sha256: $sha256
     }'
