#!/usr/bin/env bash
#
# Move a local dotfile/config (file or directory) from this host's LOCAL
# home into PSI's "Partially Shared Home" (~/network_drives), and register
# it in ~/network_drives/home/linux_home_links.yaml so it follows you to
# every other host. Companion to ~/sync_personal_config, which only
# seeds two hardcoded items (.ipython, .condarc) and never auto-edits an
# existing linux_home_links.yaml. This script is general-purpose, diffs
# before overwriting anything already staged on the network drive, and
# (carefully, with your confirmation) inserts the new entry into an
# existing linux_home_links.yaml rather than just printing a snippet.
#
# Background reading: migrate_to_network_drive.md (same directory, companion
# doc) and https://linux.psi.ch/documentation/user-guide/personal_drive/
#
# IMPORTANT LIMITATION (confirmed by hand): ~/network_drives is an SMB
# mount (via gvfs) that does NOT support the atomic rename/mkstemp writes
# git needs for its object store - `git init` or any commit there fails
# with "Operation not supported (95)". This script therefore REFUSES to
# migrate anything containing a .git directory. Git repos (gopass stores
# included) must stay in local home (or a real NFS mount like /sf/bernina -
# see gopass.md, same directory) and sync via `git`/`gopass sync` against a
# real git remote instead.
#
# Usage:
#   ./migrate_to_network_drive <path-relative-to-HOME> [link|copy] [dest-name] [from-host]
#
# Examples:
#   ./migrate_to_network_drive .npmrc
#   ./migrate_to_network_drive .config/htop copy htop-config
#   ./migrate_to_network_drive .npmrc link "" saresb-cons-07   # pull the copy from another host first
#
# [from-host] is optional. If the local path doesn't exist on this host at
# all, you'll be offered an scp fetch from another host interactively
# (default: saresb-vcons-01). Pass [from-host] explicitly to always fetch a
# fresh copy from that host and diff it against whatever's here locally,
# e.g. when consolidating several hosts' independent copies of the same
# dotfile onto one canonical version.
#
# IDEMPOTENT RE-REGISTRATION: if the local path is already a symlink/copy
# that matches the network drive exactly (e.g. re-running this to register
# one more per-file entry inside a directory you already split up by hand -
# see the .ssh/* entries in linux_home_links.yaml for the case this was
# added for), the seed-copy step is skipped entirely - no diff prompt, no
# "overwrite?" confirmation, straight to registering the yaml entry.
#
set -euo pipefail

LOCAL_REL="${1:?Usage: $0 <path-relative-to-HOME> [link|copy] [dest-name] [from-host]}"
MODE="${2:-link}"
DEST_NAME="${3:-}"
FROM_HOST="${4:-}"
DEFAULT_SCP_HOST="saresb-vcons-01"

if [[ "$MODE" != "link" && "$MODE" != "copy" ]]; then
    echo "ERROR: mode must be 'link' or 'copy', got '$MODE'" >&2
    exit 1
fi

SRC="${HOME}/${LOCAL_REL}"
NETWORK_DRIVES="${HOME}/network_drives"
HOME_DRIVE="${NETWORK_DRIVES}/home"
CONFIG_DIR="${HOME_DRIVE}/linux_config"
LINKS_FILE="${HOME_DRIVE}/linux_home_links.yaml"
[[ -f "${LINKS_FILE}" ]] || LINKS_FILE="${HOME_DRIVE}/.linux_home_links.yaml"

log() { echo "==> $*"; }
ask() {
    # ask "prompt" -> 0 (yes) or 1 (no)
    local reply
    read -r -p "$1 [y/N] " reply
    [[ "$reply" =~ ^[Yy]$ ]]
}

# --- optionally fetch the source from another host via scp first -----------
# (PSI accounts are the same username everywhere, so plain host:path works;
# the remote path is relative to $HOME on that host too, same as locally)

fetch_from_host() {
    local host="$1" tmp
    tmp="$(mktemp -d)"
    log "fetching ${host}:${LOCAL_REL} via scp ..."
    if ! scp -rq "${host}:${LOCAL_REL}" "${tmp}/fetched" 2>&1; then
        echo "ERROR: scp from ${host}:${LOCAL_REL} failed" >&2
        rm -rf "${tmp}"
        exit 1
    fi
    if [[ -e "${SRC}" ]]; then
        log "comparing the ${host} copy against local ${SRC}"
        if [[ -d "${SRC}" ]]; then
            diff -rq "${SRC}/" "${tmp}/fetched/" || true
        else
            diff -u "${SRC}" "${tmp}/fetched" || true
        fi
        if ! ask "Replace local ${SRC} with the copy fetched from ${host}?"; then
            log "keeping local ${SRC} - discarding the fetched copy"
            rm -rf "${tmp}"
            return 0
        fi
        rm -rf "${SRC}"
    else
        mkdir -p "$(dirname "${SRC}")"
    fi
    mv "${tmp}/fetched" "${SRC}"
    rmdir "${tmp}" 2>/dev/null || true
    log "local ${SRC} now matches ${host}"
}

if [[ -n "${FROM_HOST}" ]]; then
    fetch_from_host "${FROM_HOST}"
elif [[ ! -e "${SRC}" ]]; then
    log "${SRC} does not exist on this host"
    if ask "Fetch it via scp from another host?"; then
        read -r -p "Host [${DEFAULT_SCP_HOST}]: " reply_host
        fetch_from_host "${reply_host:-${DEFAULT_SCP_HOST}}"
    fi
fi

# --- sanity checks ----------------------------------------------------------

if [[ ! -e "${SRC}" ]]; then
    echo "ERROR: ${SRC} does not exist (and wasn't fetched from another host)" >&2
    exit 1
fi

if [[ -e "${SRC}/.git" || ( -f "${SRC}" && "$(basename "${SRC}")" == ".git" ) ]] \
   || find "${SRC}" -maxdepth 3 -name .git -print -quit 2>/dev/null | grep -q .; then
    cat >&2 <<EOF
ERROR: ${SRC} contains a .git directory.

~/network_drives is an SMB mount that cannot host a git repository - git's
object writes fail there with "Operation not supported". This was tested
by hand while setting up the team gopass store; see gopass.md (same
directory), "Limitations & workarounds", for the real fix - a real NFS
mount like /sf/bernina works fine for git, or independent per-host clones
synced via git/gitea.

Refusing to migrate a git repository onto the network drive.
EOF
    exit 1
fi

if [[ ! -d "${HOME_DRIVE}" ]]; then
    log "network_drives/home not found - running setup-network-drives"
    setup-network-drives || true
fi
if [[ ! -d "${HOME_DRIVE}" ]]; then
    echo "ERROR: ${HOME_DRIVE} still missing. Run 'setup-network-drives' manually." >&2
    exit 1
fi

# --- work out the destination -----------------------------------------------

if [[ -z "${DEST_NAME}" ]]; then
    # default: strip a single leading dot, replace remaining '/' with '-'
    DEST_NAME="${LOCAL_REL#.}"
    DEST_NAME="${DEST_NAME//\//-}"
fi
DEST="${CONFIG_DIR}/${DEST_NAME}"

# depth of LOCAL_REL below $HOME, needed to compute a working relative
# symlink target - see the companion doc for why this matters: the PSI
# link-creation tool does NOT adjust for nesting, so a naive target only
# resolves correctly for items directly under $HOME.
IFS='/' read -ra _parts <<< "${LOCAL_REL}"
DEPTH=$((${#_parts[@]} - 1))
UPDIRS=""
for ((i = 0; i < DEPTH; i++)); do UPDIRS="../${UPDIRS}"; done
LINK_TARGET="${UPDIRS}network_drives/home/linux_config/${DEST_NAME}"

log "local path:   ${SRC}"
log "network path: ${DEST}"
log "mode:         ${MODE}"
[[ "${MODE}" == "link" ]] && log "symlink target (relative, depth ${DEPTH}): ${LINK_TARGET}"

# --- check whether local and network are already identical -----------------
#
# Common when re-running this to register one more file out of a directory
# you already split up by hand (e.g. adding another per-file entry inside
# .ssh/ - see linux_home_links.yaml), or when SRC is already a symlink that
# resolves straight to DEST. In that case there's nothing to seed or
# confirm - skip straight to yaml registration below.

ALREADY_IN_SYNC=0
if [[ -e "${DEST}" ]]; then
    if [[ -d "${SRC}" ]]; then
        diff -rq "${SRC}/" "${DEST}/" >/dev/null 2>&1 && ALREADY_IN_SYNC=1
    else
        cmp -s "${SRC}" "${DEST}" && ALREADY_IN_SYNC=1
    fi
fi

mkdir -p "${CONFIG_DIR}"

if [[ "${ALREADY_IN_SYNC}" == "1" ]]; then
    log "${DEST} already matches ${SRC} - skipping the copy step"
else
    # --- diff before overwriting anything already on the network drive -----

    if [[ -e "${DEST}" ]]; then
        log "${DEST} already exists - showing differences before touching anything"
        if [[ -d "${SRC}" ]]; then
            diff -rq "${SRC}/" "${DEST}/" || true
            if ask "Show full recursive diff?"; then
                diff -ru "${DEST}/" "${SRC}/" || true
            fi
        else
            diff -u "${DEST}" "${SRC}" || true
        fi
        if ! ask "Overwrite ${DEST} with the contents of ${SRC}?"; then
            log "aborted - nothing changed"
            exit 0
        fi
    fi

    # --- seed the network drive (copy, not move - original stays put until
    #     the symlink swap below succeeds, so a failure here can't lose data)
    #
    # NOTE: no -a/-p (preserve mode/owner) here - tested by hand, the SMB
    # mount rejects permission/ownership writes ("Operation not supported")
    # even for plain files, not just git's internals. Content copies fine;
    # just don't ask it to also preserve perms.

    if [[ -d "${SRC}" ]]; then
        rsync -rlt --delete --no-perms --no-owner --no-group "${SRC}/" "${DEST}/"
    else
        cp "${SRC}" "${DEST}"
    fi
    log "copied to ${DEST}"
fi

# --- for link mode: replace the local copy with a symlink ------------------

if [[ "${MODE}" == "link" ]]; then
    if [[ -L "${SRC}" && "$(readlink -f "${SRC}")" == "$(readlink -f "${DEST}")" ]]; then
        log "${SRC} is already a symlink resolving to ${DEST} - nothing to swap"
    else
        if ! ask "Replace local ${SRC} with a symlink to the network copy?"; then
            log "left ${SRC} as a plain copy - re-run and confirm this step when ready"
            log "(until then this is NOT yet wired up as a link: entry)"
            exit 0
        fi
        rm -rf "${SRC}"
        ln -s "${LINK_TARGET}" "${SRC}"
        if [[ "$(readlink -f "${SRC}")" != "$(readlink -f "${DEST}")" ]]; then
            echo "ERROR: symlink does not resolve to ${DEST} - something is wrong with the depth math, not touching linux_home_links.yaml" >&2
            exit 1
        fi
        log "symlink verified: ${SRC} -> ${LINK_TARGET}"
    fi
fi

# --- register the entry in linux_home_links.yaml ----------------------------

SECTION="${MODE}"   # 'link' or 'copy'
KEY_LINE="  '${LOCAL_REL}':"
if [[ "${MODE}" == "link" ]]; then
    VALUE_LINE="     to: '${LINK_TARGET}'"
else
    VALUE_LINE="     from: 'network_drives/home/linux_config/${DEST_NAME}'"
fi

if [[ -f "${LINKS_FILE}" ]] && grep -qF "'${LOCAL_REL}':" "${LINKS_FILE}"; then
    log "an entry for '${LOCAL_REL}' already exists in ${LINKS_FILE} - not touching it"
    log "verify it matches what was just set up:"
    grep -A1 -F "'${LOCAL_REL}':" "${LINKS_FILE}"
elif [[ -f "${LINKS_FILE}" ]] && grep -qE "^${SECTION}:" "${LINKS_FILE}"; then
    awk -v key="${KEY_LINE}" -v val="${VALUE_LINE}" -v section="^${SECTION}:" '
        { print }
        $0 ~ section && !done { print key; print val; done=1 }
    ' "${LINKS_FILE}" > "${LINKS_FILE}.new"
    echo "--- proposed change to ${LINKS_FILE} ---"
    diff -u "${LINKS_FILE}" "${LINKS_FILE}.new" || true
    echo "-----------------------------------------"
    if ask "Apply this change?"; then
        BACKUP="${LINKS_FILE}.bak.$(date +%Y%m%d%H%M%S)"
        cp "${LINKS_FILE}" "${BACKUP}"
        mv "${LINKS_FILE}.new" "${LINKS_FILE}"
        log "updated ${LINKS_FILE} (backup: ${BACKUP})"
    else
        rm -f "${LINKS_FILE}.new"
        log "left ${LINKS_FILE} unchanged. Add manually:"
        echo "${SECTION}:"
        echo "${KEY_LINE}"
        echo "${VALUE_LINE}"
    fi
else
    log "no '${SECTION}:' section found in ${LINKS_FILE:-<none>} - add manually:"
    echo "${SECTION}:"
    echo "${KEY_LINE}"
    echo "${VALUE_LINE}"
fi

# --- apply now ---------------------------------------------------------------

if ask "Apply now via create-automatic-links-in-home.service?"; then
    systemctl --user restart create-automatic-links-in-home.service \
        && log "applied" \
        || log "could not restart the service here - it will run on next login/ssh/become"
fi

log "done."
