#!/usr/bin/env bash
set -euo pipefail
# POS: share nfs-client — Mount NFS shares (ephemeral or persistent systemd mount units)
# POS_SUBCMDS: mount unmount list persist unpersist menu

source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
source "$(dirname "$0")/../lib/share-lib.sh" 2>/dev/null || source "$(dirname "$0")/share-lib.sh"

# Env seam (testable): where persistent .mount units are written.
UNIT_DIR="${UNIT_DIR:-/etc/systemd/system}"

command -v mount.nfs &>/dev/null || err "mount.nfs not found (install nfs-common)"
command -v systemd-escape &>/dev/null || err "systemd-escape not found"

usage() {
    cat <<EOF
Usage: pos share nfs-client <command> [args]

Mount and manage NFS shares from remote servers (nfs-common).

Commands:
  mount <server:export> <local-dir>    One-shot mount (creates local-dir if needed)
  unmount <local-dir>                   Unmount the share (idempotent: rc 0 when
                                        nothing is mounted)
  list                                  Show active NFS mounts
  persist <server:export> <local-dir>   Persistent mount via a systemd .mount unit
                                        (ordered after network-online.target)
  unpersist <local-dir>                 Stop, disable and remove the mount unit
  menu                                  Interactive browser (server → export → mountpoint)

Run without arguments to open the interactive menu.

Examples:
  pos share nfs-client mount 100.100.100.1:/srv/media /mnt/nfs/media
  pos share nfs-client persist 100.100.100.1:/srv/media /mnt/nfs/media
  pos share nfs-client list
  pos share nfs-client unmount /mnt/nfs/media
  pos share nfs-client menu
EOF
    exit 0
}

validate_share() {
    local what="$1"
    case "$what" in
        /*) err "Invalid share '$what' — expected <server:export> (e.g. 10.0.0.5:/srv/data)" ;;
        *:*) ;;
        *) err "Invalid share '$what' — expected <server:export> (e.g. 10.0.0.5:/srv/data)" ;;
    esac
}

validate_dir() {
    local where="$1"
    case "$where" in
        /*) ;;
        *) err "Mount point must be an absolute path: $where" ;;
    esac
}

cmd_mount() {
    local what="$1" where="$2"
    validate_share "$what"
    validate_dir "$where"

    sudo mkdir -p "$where"
    sudo mount -t nfs -o rw,noatime "$what" "$where"
    log "Mounted $what at $where"
}

cmd_unmount() {
    local where="$1"
    validate_dir "$where"

    if ! findmnt -r -n -o TARGET -t nfs,nfs4 2>/dev/null | grep -qxF "$where"; then
        # Idempotent no-op — rc 0 whether or not a unit exists. A persisted
        # boot-time .mount that is not currently mounted usually means the
        # unit failed or was stopped; say so instead of a bare nothing-to-do.
        if persisted_nfs_at "$where"; then
            warn "$where has a persistent NFS mount unit (${PERSISTED_UNIT}) — not currently mounted."
            log "Check it: systemctl status ${PERSISTED_UNIT%.mount} — or remove the persistence: menu option 5 (pos share nfs-client unpersist $where)"
        else
            log "$where is not mounted as NFS — nothing to do"
        fi
        return 0
    fi
    sudo umount "$where"
    log "Unmounted $where"
}

# Persisted Type=nfs/nfs4 unit declaring Where=<path>? Sets PERSISTED_UNIT.
persisted_nfs_at() { # <path> — rc 0 persisted · rc 1 not persisted
    local uf
    PERSISTED_UNIT=""
    for uf in "${UNIT_DIR}"/*.mount; do
        [ -f "$uf" ] || continue
        grep -q '^Type=nfs' "$uf" || continue
        if [ "$(sed -n 's/^Where=//p' "$uf")" = "$1" ]; then
            PERSISTED_UNIT="$(basename "$uf")"
            return 0
        fi
    done
    return 1
}

cmd_list() {
    local out
    if out="$(findmnt -t nfs,nfs4 2>/dev/null)" && [ "$(grep -c . <<<"$out")" -gt 1 ]; then
        printf '%s\n' "$out"
    else
        echo "No NFS mounts"
    fi
}

write_mount_unit() { # <unit_file> <what> <where>
    cat <<UNIT | sudo tee "$1" >/dev/null
[Unit]
Description=NFS mount of ${2} at ${3}
After=network-online.target
Wants=network-online.target

[Mount]
What=${2}
Where=${3}
Type=nfs
Options=defaults,_netdev,rw,noatime
UNIT
}

show_mount_unit() { # <what> <where>  (dry-run preview)
    cat <<UNIT
[Unit]
Description=NFS mount of ${1} at ${2}
After=network-online.target
Wants=network-online.target

[Mount]
What=${1}
Where=${2}
Type=nfs
Options=defaults,_netdev,rw,noatime
UNIT
}

cmd_persist() {
    local what="$1" where="$2"
    validate_share "$what"
    validate_dir "$where"

    unit="$(systemd-escape --path --suffix=mount "$where")"
    unit_file="${UNIT_DIR}/${unit}"

    if [ -f "$unit_file" ]; then
        if [ -t 0 ]; then
            # FLAGGED DELTA: interactive overwrite now confirms first.
            confirm "Unit ${unit} already exists — overwrite?" n ||
                { warn "Aborted — ${unit_file} left untouched"; return 1; }
        else
            warn "Overwriting existing unit ${unit}"
        fi
    fi

    sudo mkdir -p "$where"
    if [ "${DRY_RUN:-0}" -eq 1 ]; then
        log "(dry-run) would write ${unit_file}:"
        show_mount_unit "$what" "$where"
    else
        write_mount_unit "$unit_file" "$what" "$where"
        sudo systemctl daemon-reload
        sudo systemctl enable --now "$unit"

        # Verify the export actually mounted; roll back the unit if not.
        local i mounted=1
        for i in 1 2 3 4 5; do
            if findmnt -r -n -o TARGET -t nfs,nfs4 2>/dev/null | grep -qxF "$where"; then
                mounted=0
                break
            fi
            sleep 1
        done
        if [ "$mounted" -ne 0 ]; then
            warn "Unit enabled but ${where} never appeared among NFS mounts — rolling back"
            sudo systemctl disable "$unit" 2>/dev/null || true
            sudo systemctl stop "$unit" 2>/dev/null || true
            sudo rm -f "$unit_file"
            sudo systemctl daemon-reload
            err "Persistent mount failed — unit removed (${unit})"
        fi
    fi
    log "Persistent NFS mount: ${what} → ${where} (${unit})"
    notify_send "NFS mount persisted: ${what} → ${where}"
}

cmd_unpersist() {
    local where="$1"
    validate_dir "$where"

    unit="$(systemd-escape --path --suffix=mount "$where")"
    unit_file="${UNIT_DIR}/${unit}"

    if [ ! -f "$unit_file" ]; then
        # Idempotent no-op — rc 0 whether or not anything is configured.
        # Return, not exit: a bogus typed path reached from the menu must
        # not kill the whole session. Mirror of cmd_unmount's guidance for
        # a persisted unit under a non-escape-derived filename.
        if persisted_nfs_at "$where"; then
            warn "$where has a persistent NFS mount unit (${PERSISTED_UNIT}) under a non-standard unit name."
            log "Check it: systemctl status ${PERSISTED_UNIT%.mount} — or find it in the list: pos share nfs-client list"
        else
            warn "No systemd mount unit for $where (${unit})"
        fi
        return 0
    fi

    sudo systemctl disable "$unit" 2>/dev/null || true
    sudo systemctl stop "$unit" 2>/dev/null || true
    sudo rm -f "$unit_file"
    sudo systemctl daemon-reload
    log "Removed persistent NFS mount: $where"
    notify_send "NFS persistent mount removed: $where"
}

# ── Interactive menu flows ─────────────────────────────────────

# Mountpoint picker — local port of the share_pick primitive with exactly
# two deltas: the hint line offers `n=new`, and typing n runs the
# create-new-dir flow below. share_pick cannot intercept `n` (it filters on
# it) and lib/menu-lib.sh is shared, so the fork lives here. Rendering of
# numbered picks / text filter / 0=back is byte-identical to menu_pick.
# stdout: chosen item text (or the freshly created dir) · rc 1 = back/cancel.
pick_mountpoint() {
    local prompt="$1"; shift
    local -a items=("$@")
    if [ "${#items[@]}" -eq 0 ]; then
        return 1
    fi
    if ! [ -t 0 ]; then
        printf '[!] Interactive picker needs a terminal.\n' >&2
        return 1
    fi
    local filter="" ans i n total=${#items[@]} made
    local -a shown=()
    while true; do
        shown=()
        for ((i = 0; i < total; i++)); do
            if [ -z "$filter" ] || [[ "${items[$i],,}" == *"${filter,,}"* ]]; then
                shown+=("${items[$i]}")
            fi
        done
        n=${#shown[@]}
        {
            echo
            if [ -n "$filter" ]; then
                printf -- "-- %d of %d match '%s' --\n" "$n" "$total" "$filter"
            else
                printf -- "-- %d available --\n" "$total"
            fi
            if [ "$n" -eq 0 ]; then
                printf '[!] no matches — enter nothing or / to clear the filter\n' >&2
            else
                for ((i = 0; i < n; i++)); do
                    printf ' %2d) %s\n' $((i + 1)) "${shown[$i]}"
                done
            fi
        } >&2
        if ! read -rp "${prompt} [1-${n}], n=new, text=filter, 0=back " ans; then
            return 1                     # EOF — cancel
        fi
        case "$ans" in
            "")  [ -z "$filter" ] || filter="" ; continue ;;
            "/") filter="" ; continue ;;
            0 | q | Q | b | B) return 1 ;;
            n | N)
                made="$(ask_new_mountpoint)" && { echo "$made"; return 0; }
                continue                 # declined/invalid/mkdir-failed → redraw
                ;;
            *[!0-9]*)
                filter="$ans"
                continue
                ;;
            *)
                if (( ans >= 1 && ans <= n )); then
                    echo "${shown[$((ans - 1))]}"
                    return 0
                fi
                echo "Unknown choice." >&2
                ;;
        esac
    done
}

# Create-new-dir flow behind the picker's `n` key. Validates the shape
# (absolute, no trailing slash), confirm-gates the creation, then mkdir -p.
# Any decline, invalid input, EOF or mkdir failure is a warning + rc 1 —
# the picker redraws, the tool never aborts.
ask_new_mountpoint() { # stdout: created dir · rc 1 = cancelled/failed
    # NOTE: runs inside $( ) from the picker — every display line MUST go to
    # stderr (menu-lib contract: display → stderr, result → stdout).
    local dir
    dir="$(share_ask_value "New mountpoint (absolute path)")" || return 1
    case "$dir" in
        /*) ;;
        *) warn "'$dir' is not an absolute path — must start with /" >&2; return 1 ;;
    esac
    case "$dir" in
        */) warn "'$dir' must not end with a slash" >&2; return 1 ;;
    esac
    case "$dir" in
        /etc|/boot|/bin|/sbin|/lib|/lib64|/usr|/var|/root|/home/*/.ssh*)
            warn "Refusing system path as mountpoint" >&2
            return 1
            ;;
    esac
    confirm "Create mountpoint ${dir}?" n || return 1
    if ! run sudo mkdir -p "$dir"; then
        warn "Could not create ${dir}" >&2
        return 1
    fi
    log "Created mount point $dir" >&2
    echo "$dir"
}

menu_pick_export() { # <host> — stdout: server:export · rc 1 cancelled
    local host="$1" idx exp
    local -a exports=()
    if mapfile -t exports < <(share_nfs_exports "$host") && [ "${#exports[@]}" -gt 0 ]; then
        if idx="$(share_pick "Pick export on ${host}" "${exports[@]}")"; then
            exp="${exports[$((idx - 1))]}"
        else
            return 1
        fi
    else
        exp="$(share_ask_value "Export path on ${host} (e.g. /srv/media)")" || return 1
        [ -n "$exp" ] || { warn "No export path given"; return 1; }
    fi
    case "$exp" in
        /*) echo "${host}:${exp}" ;;
        *) echo "${host}:/${exp}" ;;
    esac
}

menu_ask_mountpoint() { # [server_path] — stdout: absolute path · rc 1 cancelled
    local srv="${1:-}" idx res cand dir known=0
    local -a cands=() dirs=()
    mapfile -t cands < <(share_folder_candidates)
    for cand in "${cands[@]}"; do
        dirs+=("${cand%% (*}")     # bare path (strip "(mounted fstype)" note)
    done
    # Same-as-server suggestion: unless the server-side path already exists
    # among the local candidates, append it as a synthetic pick so mounting
    # at a mirrored path is a normal selection.
    if [ -n "$srv" ] && [ "${#dirs[@]}" -gt 0 ] &&
        printf '%s\n' "${dirs[@]}" | grep -qxF -- "$srv"; then
        known=1
    fi
    if [ -n "$srv" ] && [ "$known" -eq 0 ]; then
        cands+=("${srv}   (as on server)")
    fi
    if [ "${#cands[@]}" -gt 0 ]; then
        if res="$(pick_mountpoint "Mountpoint" "${cands[@]}")"; then
            dir="${res%% (*}"              # strip "(as on server)"/mount note
            case "$dir" in
                /etc|/boot|/bin|/sbin|/lib|/lib64|/usr|/var|/root|/home/*/.ssh*)
                    warn "Refusing system path as mountpoint"
                    return 1
                    ;;
                *)
                    echo "$dir"
                    return 0
                    ;;
            esac
        fi
    fi
    dir="$(share_ask_value "Mountpoint (absolute path)")" || return 1
    [ -n "$dir" ] || { warn "No mountpoint given"; return 1; }
    echo "$dir"
}

menu_mount() {
    local mode="$1" host what where
    host="$(share_ask_value "NFS server (host or IP)")" || return 1
    [ -n "$host" ] || { warn "No server given"; return 1; }

    if ! share_port_probe "$host" 2049; then
        warn "${host} does not answer on TCP/2049 (nfsd down, or a firewall blocks it)."
        confirm "Try anyway?" n || return 1
    fi

    what="$(menu_pick_export "$host")" || return 1
    where="$(menu_ask_mountpoint "${what#*:}")" || return 1   # ${what#*:} = server-side export path

    if [ "$mode" = "persist" ]; then
        cmd_persist "$what" "$where"
    else
        cmd_mount "$what" "$where"
    fi
}

menu_unmount() {
    local idx row src where i
    local -a tgts=() srcs=() items=()
    # Same enumeration source as the `list` view (findmnt -t nfs,nfs4),
    # reduced to TARGET|SOURCE rows.
    while IFS= read -r row; do
        [ -n "$row" ] || continue
        tgts+=("${row%%|*}")
        srcs+=("${row#*|}")
    done < <(findmnt -rn -o TARGET,SOURCE -t nfs,nfs4 2>/dev/null |
        awk '{ src=$NF; $NF=""; sub(/[ \t]+$/, ""); print $0 "|" src }')
    if [ "${#tgts[@]}" -eq 0 ]; then
        log "No active NFS mounts"
        where="$(share_ask_value "Local mountpoint to unmount")" || return 1
        [ -n "$where" ] || return 1
        cmd_unmount "$where"
        return 0
    fi
    for ((i = 0; i < ${#tgts[@]}; i++)); do
        items+=("${tgts[$i]}  ← ${srcs[$i]}")
    done
    idx="$(share_pick "Unmount which NFS mount?" "${items[@]}")" || return 1
    where="${tgts[$((idx - 1))]}"
    src="${srcs[$((idx - 1))]}"
    confirm "Unmount ${where} (from ${src})?" n || { log "Cancelled"; return 1; }
    cmd_unmount "$where"
}

menu_unpersist() {
    local idx uf where unit
    local -a items=() paths=()
    for uf in "${UNIT_DIR}"/*.mount; do
        grep -q '^Type=nfs' "$uf" 2>/dev/null || continue
        where="$(sed -n 's/^Where=//p' "$uf")"
        [ -n "$where" ] || continue
        paths+=("$where")
        items+=("$where")
    done
    if [ "${#items[@]}" -gt 0 ]; then
        idx="$(share_pick "Remove which persistent NFS mount?" "${items[@]}")" || return 1
        where="${paths[$((idx - 1))]}"
    else
        where="$(share_ask_value "Local mountpoint whose unit to remove")" || return 1
        [ -n "$where" ] || return 1
    fi
    cmd_unpersist "$where"
}

run_menu() {
    share_menu_guard || exit 1
    while true; do
        local choice
        choice="$(share_menu_run "NFS client" \
            "Mount an export (one-shot)" \
            "Persist an export (systemd .mount unit)" \
            "List active NFS mounts" \
            "Unmount a mounted share" \
            "Remove a persistent mount")" || return 0
        case "$choice" in
            # Handlers return nonzero on cancel/back — normalized here so a
            # cancel can never reach set -e and kill the whole session.
            1) menu_mount ephemeral || true ;;
            2) menu_mount persist || true ;;
            3) cmd_list || true ;;
            4) menu_unmount || true ;;
            5) menu_unpersist || true ;;
        esac
    done
}

cmd="${1:-}"
case "$cmd" in
    -h|--help) usage ;;
    ""|menu)
        run_menu
        exit 0
        ;;
    mount|unmount|list|persist|unpersist) ;;
    *) err "Unknown command '$cmd' (see --help)" ;;
esac

case "$cmd" in
    mount)
        [ $# -ge 3 ] || err "Usage: pos share nfs-client mount <server:export> <local-dir>"
        cmd_mount "$2" "$3"
        ;;

    unmount)
        [ $# -ge 2 ] || err "Usage: pos share nfs-client unmount <local-dir>"
        cmd_unmount "$2"
        ;;

    list)
        cmd_list
        ;;

    persist)
        [ $# -ge 3 ] || err "Usage: pos share nfs-client persist <server:export> <local-dir>"
        cmd_persist "$2" "$3"
        ;;

    unpersist)
        [ $# -ge 2 ] || err "Usage: pos share nfs-client unpersist <local-dir>"
        cmd_unpersist "$2"
        ;;
esac
