#!/usr/bin/env bash
# =============================================================================
# onx-file-move — Move/rename file or directory under /home/<user>
#
# Purpose:
#   `mv` equivalent with strict path validation. Atomic rename on the same
#   filesystem; falls back to cp -a + rm -rf when src/dst sit on different
#   devices (rare under /home but possible with bind mounts / quotas).
#
# Input (stdin JSON):
#   {
#     "username":   "onx_xxxx",
#     "src":        "public_html/old.html",
#     "dst":        "public_html/new.html",
#     "force":      false        -- optional; default false (refuse overwrite)
#   }
#
# Output (stdout JSON):
#   {
#     "username":    "onx_xxxx",
#     "src":         "/home/onx_xxxx/public_html/old.html",
#     "dst":         "/home/onx_xxxx/public_html/new.html",
#     "is_dir":      false,
#     "cross_device": false,
#     "overwritten": false
#   }
#
# Exit codes: 0=ok 1=invalid-input 2=preflight-fail 3=execution-fail
# Deployed to: /usr/local/onoxsoft/bin/onx-file-move
# =============================================================================

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 mv
require_cmd stat

onx_json_input

USERNAME="$(onx_json_field username)"
SRC_REL="$(onx_json_field src)"
DST_REL="$(onx_json_field dst)"
FORCE="$(onx_json_get_bool "$INPUT" force false)"

onx_validate_username "$USERNAME"
[[ -z "${SRC_REL}" ]] && onx_die 1 "src is required"
[[ -z "${DST_REL}" ]] && onx_die 1 "dst is required"

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

SRC_ABS="$(realpath -e "${HOME_DIR}/${SRC_REL#/}" 2>/dev/null)" \
    || onx_die 1 "source not found: ${SRC_REL}"

DST_INPUT="${HOME_DIR}/${DST_REL#/}"
DST_ABS="$(realpath -m "${DST_INPUT}" 2>/dev/null || printf '%s' "${DST_INPUT}")"

case "${SRC_ABS}" in
    "${HOME_DIR}"|"${HOME_DIR}"/*) ;;
    *) onx_die 1 "src escapes /home/${USERNAME}: ${SRC_REL}" ;;
esac
case "${DST_ABS}" in
    "${HOME_DIR}"|"${HOME_DIR}"/*) ;;
    *) onx_die 1 "dst escapes /home/${USERNAME}: ${DST_REL}" ;;
esac

[[ "${SRC_ABS}" == "${DST_ABS}" ]] && onx_die 1 "src and dst are identical"

OVERWRITTEN="false"
if [[ -e "${DST_ABS}" ]]; then
    if [[ "${FORCE}" != "true" ]]; then
        onx_die 2 "destination exists (use force=true to overwrite): ${DST_REL}"
    fi
    OVERWRITTEN="true"
fi

if [[ -d "${SRC_ABS}" ]]; then
    case "${DST_ABS}/" in
        "${SRC_ABS}/"*) onx_die 1 "dst is inside src — cannot move into itself" ;;
    esac
fi

DST_PARENT="$(dirname "${DST_ABS}")"
[[ -d "${DST_PARENT}" ]] || mkdir -p "${DST_PARENT}"

IS_DIR="false"
[[ -d "${SRC_ABS}" ]] && IS_DIR="true"

# ── Atomic rename, with cross-device fallback ───────────────────────────────
CROSS_DEVICE="false"

# Detect cross-device early (Linux EXDEV) — both paths' device IDs.
SRC_DEV="$(stat -c '%d' "${SRC_ABS}" 2>/dev/null || echo 0)"
DST_DEV="$(stat -c '%d' "${DST_PARENT}" 2>/dev/null || echo 0)"

if [[ "${SRC_DEV}" == "${DST_DEV}" ]]; then
    mv -f -- "${SRC_ABS}" "${DST_ABS}" 2>/dev/null \
        || onx_die 3 "mv failed: ${SRC_REL} → ${DST_REL}"
else
    CROSS_DEVICE="true"
    # Cross-device: cp -a then rm -rf src. Roll back the cp on rm failure.
    onx_log "file-move: cross-device fallback (${SRC_DEV} -> ${DST_DEV})"
    cp -a -- "${SRC_ABS}" "${DST_ABS}" 2>/dev/null \
        || onx_die 3 "cross-device cp failed: ${SRC_REL} → ${DST_REL}"
    if ! rm -rf -- "${SRC_ABS}" 2>/dev/null; then
        rm -rf -- "${DST_ABS}" 2>/dev/null || true
        onx_die 3 "cross-device rm failed (rolled back): ${SRC_REL}"
    fi
fi

onx_log "file-move: user=${USERNAME} src='${SRC_REL}' dst='${DST_REL}' cross_device=${CROSS_DEVICE} overwritten=${OVERWRITTEN}"

jq -nc \
    --arg username "${USERNAME}" \
    --arg src "${SRC_ABS}" \
    --arg dst "${DST_ABS}" \
    --argjson is_dir       "$([[ "${IS_DIR}"       == "true" ]] && echo true || echo false)" \
    --argjson cross_device "$([[ "${CROSS_DEVICE}" == "true" ]] && echo true || echo false)" \
    --argjson overwritten  "$([[ "${OVERWRITTEN}"  == "true" ]] && echo true || echo false)" \
    '{
        username: $username,
        src: $src,
        dst: $dst,
        is_dir: $is_dir,
        cross_device: $cross_device,
        overwritten: $overwritten
     }'
