#!/usr/bin/env bash
set -euo pipefail
# POS: system backup — Encrypted (AES-256) folder snapshots (tar + gpg)
# POS_FLAGS: --service
# POS_CONFIG: notify | notify.env | NOTIFY_PLATFORM=:Comma-separated notify platforms (default telegram) — shared by backup, firewall, share nfs client/server

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"

load_system_env
EFF_ROOTS="${BACKUP_SERVICE_ROOTS:-/srv $HOME/srv}"

trap 'notify_send "Backup FAILED: ${FOLDER:-unknown}"' ERR

usage() {
    cat <<EOF
Usage: pos system backup <folder-path>
       pos system backup --service

Create a gpg-encrypted (AES-256) tar.gz snapshot of a folder and verify it.
The archive password is prompted twice and never stored.

Modes:
  <folder-path>   Back up that folder directly.
  --service       List folders under /srv and ~/srv, pick one, back it up.

The final artifact <name>_<date>.tar.gz.gpg is written to the current directory.
After it verifies, connected USB storage is offered: the copy lands in
<usb>/backups/ and is sha256-verified 100% before it is announced. A stick
that is plugged in but not mounted is offered a mount first (sudo, mirrors
the usb-automount scheme) before the copy.

Environment:
  BACKUP_SERVICE_ROOTS   Space-separated roots for --service
                         (effective: ${EFF_ROOTS})
  BACKUP_USB_ROOT        USB root to copy finished backups to
                         (default: auto-detect mounted USB storage)
  BACKUP_MOUNT_BASE      Where to mount an unmounted USB stick offered
                         during the copy (default: /media)
  (loaded from ~/.config/linux_post_install/system.env unless exported)
EOF
    exit 0
}

command -v tar &>/dev/null || err "tar not found"
command -v gpg &>/dev/null || err "gpg not found (install gnupg)"

# ── USB copy (optional post-backup step) ─────────────────────────
# Detection runs AFTER the backup finished, so a stick plugged in while
# the archive was being made is found. The copy lands in <usb>/backups/
# and the transfer is proven 100% (sha256 source vs copy) before any
# success is announced. BACKUP_USB_ROOT pins the root and skips
# detection; otherwise USB storage is auto-detected.
#
# "Is it really USB?" — lsblk's TRAN column decides per device (rm==1
# card readers / hot-swap bays report sata and are skipped). When TRAN
# is unavailable, /dev/disk/by-id/usb-* symlinks and lsusb text
# corroborate. A stick that is plugged in but not mounted (the CLI-box
# case) is offered a mount (sudo, usb-automount scheme: /media/<label>,
# fallback /media/usb-<devname>) before the copy; cron/EOF skips.
usb_copy_offer() {
    local archive="$1" pass=0 resp="" i=0 root="" dest_dir="" dest="" src_sum="" dst_sum=""
    local -a roots=()
    local offered=0

    command -v lsblk &>/dev/null || { warn "lsblk not found — USB copy skipped"; return 0; }
    command -v jq    &>/dev/null || { warn "jq not found — USB copy skipped"; return 0; }

    section "USB copy"

    while :; do
        pass=$((pass + 1))
        roots=()

        if [ -n "${BACKUP_USB_ROOT:-}" ]; then
            roots+=("$BACKUP_USB_ROOT")
        else
            detect_usb
            roots=("${USB_MOUNTED[@]}")

            if [ ${#roots[@]} -eq 0 ] && [ ${#USB_UNMOUNTED[@]} -gt 0 ]; then
                if [ "$offered" -ge 1 ]; then
                    warn "USB stick still not mounted — backup stays local: $archive"
                    return 0
                fi
                offered=1
                mount_offer "${USB_UNMOUNTED[@]}" || {
                    log "Skipped — backup stays local: $archive"
                    return 0
                }
                continue
            fi
        fi

        [ ${#roots[@]} -gt 0 ] && break

        # None found — offer one re-scan before giving up.
        if [ "$pass" -ge 2 ]; then
            warn "Still no USB storage detected — backup stays local: $archive"
            return 0
        fi
        warn "No USB storage detected"
        read -rp "Plug a USB drive in now and press Enter to re-check (or 's' to skip): " resp || return 0
        case "$resp" in
            s|S) log "Skipped — backup stays local: $archive"; return 0 ;;
        esac
    done

    if [ ${#roots[@]} -eq 1 ]; then
        root="${roots[0]}"
        if ! confirm "Copy backup to ${root%/}/backups/?" n; then
            log "Skipped — backup stays local: $archive"
            return 0
        fi
    else
        echo "Multiple USB storages found:"
        for i in "${!roots[@]}"; do
            printf "%2d) %s\n" "$((i + 1))" "${roots[$i]}"
        done
        read -rp "Copy backup to which one? [1-${#roots[@]}] (0 = skip): " resp || return 0
        if ! [[ "$resp" =~ ^[0-9]+$ ]] || (( resp < 1 || resp > ${#roots[@]} )); then
            log "Skipped — backup stays local: $archive"
            return 0
        fi
        root="${roots[$((resp - 1))]}"
    fi

    dest_dir="${root%/}/backups"
    dest="$dest_dir/$(basename "$archive")"

    mkdir -p "$dest_dir"
    log "Copying to $dest ..."
    cp "$archive" "$dest_dir/"
    chmod 600 "$dest" 2>/dev/null \
        || warn "Could not chmod 600 the USB copy (vfat filesystem?)"

    log "Verifying transfer (sha256)..."
    src_sum="$(sha256sum "$archive" | cut -d' ' -f1)"
    dst_sum="$(sha256sum "$dest" | cut -d' ' -f1)"
    if [ "$src_sum" != "$dst_sum" ]; then
        warn "USB copy FAILED verification — checksum mismatch:"
        warn "  source: $src_sum  $archive"
        warn "  copy  : $dst_sum  $dest"
        notify_send "USB copy FAILED for $archive — checksum mismatch on $dest"
        exit 1
    fi
    ok "Transfer verified 100% (sha256 match): $dest"
    notify_send "Backup copied to USB: $dest (sha256 verified)"
}

# ── USB detection ────────────────────────────────────────────────
# Fills USB_MOUNTED (mountpoints, one per removable USB storage) and
# USB_UNMOUNTED ("path|label|size|model" entries) from lsblk JSON.
# TRAN=="usb" is the per-device deciding signal; when TRAN is empty,
# usb_related_present() corroborates. Non-USB removables are skipped.
detect_usb() {
    local out path="" mp="" label="" size="" model="" tran="" type="" children=""
    USB_MOUNTED=()
    USB_UNMOUNTED=()
    out="$(lsblk -J -o NAME,PATH,LABEL,MOUNTPOINT,RM,TYPE,TRAN,SIZE,MODEL 2>/dev/null)" || return 0

    while IFS=$'\x1f' read -r path mp label size model tran type children; do
        [ -n "$path" ] || continue
        if [ "$tran" = "usb" ]; then
            :
        elif [ -z "$tran" ] && usb_related_present; then
            warn "TRAN unavailable — assuming USB (lsusb/by-id corroboration)"
        else
            continue
        fi
        if [ -n "$mp" ]; then
            USB_MOUNTED+=("$mp")
        elif [ "$children" = "0" ]; then
            USB_UNMOUNTED+=("$path|$label|$size|$model")
        fi
    done < <(printf '%s' "$out" | jq -r '
        .. | objects
        | select(.rm == true and (.type == "part" or .type == "disk"))
        | [.path, (.mountpoint // ""), (.label // ""), (.size // ""),
           (.model // ""), (.tran // ""), (.type // ""),
           ((.children // []) | length)]
        | join("\u001f")')
}

# Whole-system "is any USB storage attached?" — udev by-id usb-* symlinks
# are definitive; lsusb text is a secondary hint for storage-ish devices.
# BACKUP_USB_BYID points at the by-id dir (test seam; default real one).
usb_related_present() {
    [ -n "$(ls "${BACKUP_USB_BYID:-/dev/disk/by-id}"/usb-* 2>/dev/null)" ] && return 0
    command -v lsusb &>/dev/null \
        && lsusb 2>/dev/null | grep -qiE 'mass storage|card reader|flash disk|usb.*(disk|drive|storage)|reader|external'
}

# ── Mount an unmounted USB stick (CLI-box case) ──────────────────
# Asks to mount a detected-but-unmounted device (sudo, mirroring
# usb-automount: /media/<label>, fallback /media/usb-<devname>, -o
# umask=000 world-writable). Returns 0 → caller re-scans (mounted now
# or the user will mount manually); 1 → skipped/EOF (backup stays local).
mount_offer() {
    local -a devs=("$@")
    local i=0 dev="" label="" size="" model="" mp="" resp="" n=""
    local -a chosen=()

    if [ ${#devs[@]} -gt 1 ]; then
        echo "Multiple unmounted USB storages found:"
        for i in "${!devs[@]}"; do
            IFS='|' read -r dev label size model <<< "${devs[$i]}"
            printf "%2d) %s  (%s, %s)\n" "$((i + 1))" "$dev" "$size" "${model:-no label}"
        done
        read -rp "Copy backup to which one? [1-${#devs[@]}] (0 = skip): " resp || return 1
        if ! [[ "$resp" =~ ^[0-9]+$ ]] || (( resp < 1 || resp > ${#devs[@]} )); then
            return 1
        fi
        chosen=("${devs[$((resp - 1))]}")
    else
        chosen=("${devs[0]}")
    fi

    IFS='|' read -r dev label size model <<< "${chosen[0]}"
    label="${label//\//_}"
    mp="${BACKUP_MOUNT_BASE:-/media}/${label:-usb-$(basename "$dev")}"
    if [ -d "$mp" ] && mountpoint -q "$mp" 2>/dev/null; then
        n=2
        while [ -d "${mp}-${n}" ] && mountpoint -q "${mp}-${n}" 2>/dev/null; do
            n=$((n + 1))
        done
        mp="${mp}-${n}"
    fi

    echo "Found USB storage not mounted: $dev ($size, ${model:-no label})"
    if confirm "Mount it at $mp (world-writable) so the backup can go there?" n; then
        run sudo mkdir -p "$mp"
        if ! run sudo mount -o umask=000 "$dev" "$mp"; then
            run sudo mount "$dev" "$mp" || true
        fi
        if [ "${DRY_RUN:-0}" -eq 1 ] || mountpoint -q "$mp" 2>/dev/null; then
            ok "Mounted $dev at $mp"
            return 0
        fi
        warn "Mount failed — do it manually, then we'll copy:"
        log "  sudo mkdir -p $mp && sudo mount $dev $mp"
    else
        warn "Mount it manually, then we'll copy:"
        log "  sudo mkdir -p $mp"
        log "  sudo mount $dev $mp"
    fi

    read -rp "Press Enter once mounted (or 's' to skip): " resp || return 1
    case "$resp" in
        s|S) return 1 ;;
    esac
    return 0
}

SERVICE=0
case "${1:-}" in
    -h|--help) usage ;;
    --service) SERVICE=1 ;;
    "")        err "Missing folder path (or use --service)" ;;
    *)         FOLDER="$1" ;;
esac

if [ "$SERVICE" -eq 1 ]; then
    if [ -n "${BACKUP_SERVICE_ROOTS:-}" ]; then
        read -r -a roots <<< "$BACKUP_SERVICE_ROOTS"
    else
        roots=(/srv "$HOME/srv")
    fi

    idx=0
    for root in "${roots[@]}"; do
        [ -d "$root" ] || { warn "Root not found: $root"; continue; }

        dirs=()
        while IFS= read -r d; do
            dirs+=("$d")
        done < <(find "$root" -maxdepth 1 -mindepth 1 -type d | sort)

        [ ${#dirs[@]} -gt 0 ] || { warn "No folders in $root"; continue; }

        echo
        echo "${root}:"
        for d in "${dirs[@]}"; do
            idx=$((idx + 1))
            names[$idx]="$d"
            printf "%2d) %s\n" "$idx" "$d"
        done
    done

    [ "$idx" -gt 0 ] || err "No folders found under: ${roots[*]}"

    read -rp "Select folder number: " choice
    if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > idx )); then
        err "Invalid selection: $choice"
    fi
    FOLDER="${names[$choice]}"
fi

[ -d "$FOLDER" ] || err "Folder not found: $FOLDER"

NAME="$(basename "$FOLDER")"
DATE="$(date +%Y-%m-%d_%H-%M-%S)"
ARCHIVE="${NAME}_${DATE}.tar.gz"

echo
log "Creating backup..."
echo "Source : $FOLDER"
echo "Output : $ARCHIVE"

sudo tar -czvf "$ARCHIVE" -C "$(dirname "$FOLDER")" "$NAME"

log "Verifying archive..."
tar -tzf "$ARCHIVE" > /dev/null
log "Archive verified"

while true; do
    read -s -rp "Enter backup password: " PASS
    echo
    read -s -rp "Confirm backup password: " CONFIRM
    echo
    if [ -n "$PASS" ] && [ "$PASS" = "$CONFIRM" ]; then
        break
    fi
    warn "Passwords are empty or do not match — try again"
done
unset CONFIRM

log "Encrypting backup..."
gpg --batch --yes --passphrase "$PASS" --symmetric --cipher-algo AES256 "$ARCHIVE"

rm -f "$ARCHIVE"
ARCHIVE="${ARCHIVE}.gpg"
chmod 600 "$ARCHIVE"

log "Verifying encrypted backup..."
gpg --batch --quiet --passphrase "$PASS" --decrypt "$ARCHIVE" | tar -tzf - > /dev/null

unset PASS
echo
log "Backup completed: $ARCHIVE"
notify_send "Backup completed: $ARCHIVE"

# Optional: detect a USB stick connected after the backup finished, offer to
# copy the archive to <usb>/backups/, and prove the transfer 100%. From here
# on a failure is a USB-copy problem, not a backup problem.
trap 'notify_send "USB copy FAILED: ${ARCHIVE:-unknown}"' ERR
usb_copy_offer "$ARCHIVE"
