#!/usr/bin/env bash
set -euo pipefail

# seed-originals-to-spaces.sh — populate Spaces with the Photos.app `originals/`
# tree so the rclone mount (photos-originals-mount.sh) can serve it back.
#
# Correct-by-construction: each object's key is derived from the asset's REAL
# in-bundle path (via osxphotos), so we mirror Photos' actual sharded layout
# (originals/<hex>/<UUID>.ext) exactly — no guessing the directory scheme. The
# bytes come from iCloud (osxphotos --download-missing materializes originals
# that aren't on local disk).
#
# Resumable: keys already in Spaces are skipped. Bounded: a disk-headroom guard
# pauses before plum fills. NON-DESTRUCTIVE by default (originals stay in the
# bundle); pass --evict to rm each original after a verified upload to reclaim
# space as you go (needed on a near-full disk; loud warnings apply).
#
# Usage (on plum):
#   ./seed-originals-to-spaces.sh --count         # read-only: how many assets
#   ./seed-originals-to-spaces.sh                 # mirror (keep local copies)
#   ./seed-originals-to-spaces.sh --evict         # mirror + free space as we go
#   ./seed-originals-to-spaces.sh --limit 50      # first N (smoke test)

LIBRARY="${LIBRARY:-$HOME/Pictures/Photos Library.photoslibrary}"
MIN_FREE_GB="${MIN_FREE_GB:-6}"          # pause/skip-download below this headroom
STAGE="${STAGE:-$(mktemp -d /tmp/macsync-seed.XXXXXX)}"
# Download mechanism: 'applescript' drives Photos.app (needs a one-time
# "Terminal wants to control Photos" Automation consent — works for a CLI);
# 'photokit' calls PhotoKit directly (often denied to a bundle-less CLI).
# AppleScript is the default because it's the one that actually works from a
# terminal; Photos.app must be running.
SEED_DOWNLOADER="${SEED_DOWNLOADER:-applescript}"
EVICT=0; COUNT_ONLY=0; LIMIT=0; DIAGNOSE=0

while [[ $# -gt 0 ]]; do
  case "$1" in
    --evict) EVICT=1 ;;
    --count) COUNT_ONLY=1 ;;
    --diagnose) DIAGNOSE=1 ;;
    --limit) LIMIT="${2:?--limit needs N}"; shift ;;
    *) echo "unknown arg: $1" >&2; exit 1 ;;
  esac
  shift
done

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/spaces-env.sh
source "$SCRIPT_DIR/lib/spaces-env.sh"

die() { echo "error: $*" >&2; exit 1; }
log() { echo "[seed] $*"; }
cleanup() { rm -rf "$STAGE"; }
trap cleanup EXIT

# Extra export flags selecting the iCloud download mechanism.
dl_flags() { if [ "$SEED_DOWNLOADER" = "photokit" ]; then echo "--use-photokit"; fi; return 0; }

# Try to download ONE asset's original to $1 (a dir); echo 1 if a real image
# landed, else 0. Honors $SEED_DOWNLOADER. Used by the seed loop and diagnose.
try_download() {
  local dest="$1" uuid="$2"
  # shellcheck disable=SC2046
  osxphotos export "$dest" --library "$LIBRARY" --uuid "$uuid" \
    --download-missing $(dl_flags) --update --no-progress >/dev/null 2>&1 || true
  find "$dest" -type f ! -name '.osxphotos*' | grep -q . && echo 1 || echo 0
}

[ -d "$LIBRARY" ] || die "library not found: $LIBRARY"

ok() { echo "  ✓ $*"; }
bad() { echo "  ✗ $*"; FAIL=1; }

# --diagnose: check every precondition and pinpoint the blocking gate, incl. a
# live one-asset iCloud download probe. Moves no bulk data.
diagnose() {
  FAIL=0
  echo "[diagnose] preconditions for the iCloud → Spaces seed"

  command -v osxphotos >/dev/null && ok "osxphotos: $(osxphotos --version 2>/dev/null | head -n1)" \
    || bad "osxphotos missing (uv tool install osxphotos)"
  command -v rclone >/dev/null && ok "rclone: $(rclone version 2>/dev/null | head -n1)" \
    || bad "rclone missing (brew install rclone)"
  [ -f "$ACCESS_FILE" ] && [ -f "$SECRET_FILE" ] && ok "vault creds present" \
    || bad "missing $ACCESS_FILE / $SECRET_FILE"

  if [ "$FAIL" -eq 0 ]; then
    spaces_env_init
    if rclone lsd ":s3:$BUCKET" >/dev/null 2>&1; then ok "Spaces reachable + signed ($BUCKET)"
    else bad "Spaces unreachable / auth failed"; fi
  fi

  # Library read (proves Full Disk Access for enumeration). `|| true` neutralizes
  # the SIGPIPE that `head` raises in osxphotos under `set -o pipefail`.
  if [ -n "$(osxphotos query --library "$LIBRARY" --print "{uuid}" --quiet 2>/dev/null | head -n1 || true)" ]; then
    ok "library readable (Full Disk Access OK for enumeration)"
  else
    bad "cannot read library — grant the terminal Full Disk Access"
  fi

  # The decisive probe: can we actually pull ONE iCloud original? Test BOTH
  # mechanisms and report which works (Photos.app must be running for either).
  local u dl m worked=""
  u="$(osxphotos query --library "$LIBRARY" --missing --print "{uuid}" --quiet 2>/dev/null | head -n1 || true)"
  if [ -z "$u" ]; then
    ok "no iCloud-only originals to test (all already on disk?)"
  else
    pgrep -xq Photos || echo "  ! Photos.app is not running — open it (download needs it)"
    for m in applescript photokit; do
      dl="$(mktemp -d /tmp/macsync-diag.XXXX)"
      if [ "$(SEED_DOWNLOADER="$m" try_download "$dl" "$u")" = 1 ]; then
        ok "iCloud download WORKS via $m"; worked="$m"
      else
        echo "  · $m: no original retrieved"
      fi
      rm -rf "$dl"
      [ -n "$worked" ] && break
    done
    if [ -n "$worked" ]; then
      # Confirm the downloaded original resolves to a real bundle path — that is
      # what the seed keys on. If it shows a path, key-derivation is sound.
      local fp
      fp="$(osxphotos query --library "$LIBRARY" --uuid "$u" --print "{filepath}" --quiet 2>/dev/null || true)"
      if [ -n "$fp" ] && [ "$fp" != "None" ]; then
        ok "bundle path resolves: ${fp#"$LIBRARY"/}"
      else
        echo "  ! downloaded but bundle path still unresolved — tell the dev (key strategy needs the staged-file fallback)"
      fi
      echo "      → run the seed with: SEED_DOWNLOADER=$worked ./$(basename "$0") --evict"
    else
      bad "iCloud download FAILED both ways (exported 0)."
      echo "      Fix: open Photos.app; grant Terminal → System Settings → Privacy &"
      echo "      Security → Full Disk Access + Automation → Photos (the consent"
      echo "      dialog appears the first time AppleScript drives Photos). If it"
      echo "      still fails, use Photos.app 'Download Original(s)' on a batch, then"
      echo "      run the seed with --evict (no CLI download needed)."
    fi
  fi

  echo
  [ "$FAIL" -eq 0 ] && echo "[diagnose] ALL GREEN — run: $0 --count, then --limit 50, then --evict" \
    || echo "[diagnose] BLOCKED — clear the ✗ items above, then re-run --diagnose"
  exit "$FAIL"
}

[ "$DIAGNOSE" -eq 1 ] && diagnose

# osxphotos (read-only DB access; handles photolibraryd locking safely).
if ! command -v osxphotos >/dev/null; then
  command -v uv >/dev/null || die "need osxphotos — install uv (brew install uv) or osxphotos"
  log "installing osxphotos via uv…"
  uv tool install osxphotos >/dev/null
fi

free_gb() { df -g "$LIBRARY" | awk 'NR==2{print $4}'; }

# --- manifest: uuid<TAB>bundle_path  (path empty = iCloud-only, not on disk) --
# Streamed via --print (not --json: that loads the whole library into memory and
# stalls on large libraries). osxphotos prints the literal "None" when an asset
# has no on-disk original yet; we normalize that to empty.
log "enumerating assets from $LIBRARY …"
MANIFEST="$STAGE/manifest.tsv"
osxphotos query --library "$LIBRARY" --print "{uuid}|{filepath}" --quiet 2>/dev/null \
  | awk -F'|' 'NF>=1 && $1!="" { p=$2; if (p=="None") p=""; print $1 "\t" p }' > "$MANIFEST"

TOTAL=$(wc -l < "$MANIFEST" | tr -d ' ')
log "assets (excl. trash): $TOTAL"
[ "$COUNT_ONLY" -eq 1 ] && { log "count-only; nothing moved."; exit 0; }

spaces_env_init

# Pre-list existing keys once (fast resume; membership check is local).
log "listing already-seeded keys under $PREFIX …"
SEEDED="$STAGE/seeded.txt"
rclone lsf -R --files-only ":s3:$BUCKET/$PREFIX" 2>/dev/null | sort > "$SEEDED" || : > "$SEEDED"
log "already in Spaces: $(wc -l < "$SEEDED" | tr -d ' ')"

ORIG_ROOT="$LIBRARY/originals/"
done=0; up=0; skip=0; n=0

while IFS=$'\t' read -r uuid path; do
  n=$((n+1)); [ "$LIMIT" -gt 0 ] && [ "$n" -gt "$LIMIT" ] && break

  # Materialize from iCloud if the original isn't on disk yet.
  if [ -z "$path" ] || [ ! -f "$path" ]; then
    if [ "$(free_gb)" -lt "$MIN_FREE_GB" ] && [ "$EVICT" -eq 0 ]; then
      die "free space < ${MIN_FREE_GB}G and --evict not set — free space or re-run with --evict"
    fi
    # Download via the configured mechanism (AppleScript default — the one that
    # works from a terminal once Photos Automation is consented).
    # shellcheck disable=SC2046
    osxphotos export "$STAGE/dl" --library "$LIBRARY" --uuid "$uuid" \
      --download-missing $(dl_flags) --update --no-progress >/dev/null 2>&1 || true
    # Re-resolve the now-downloaded bundle path ("None" → empty = still missing).
    path="$(osxphotos query --library "$LIBRARY" --uuid "$uuid" --print "{filepath}" --quiet 2>/dev/null || true)"
    [ "$path" = "None" ] && path=""
    rm -rf "$STAGE/dl"
  fi
  [ -n "$path" ] && [ -f "$path" ] || { log "skip (no original): $uuid"; continue; }

  # Key = real bundle path relative to originals/  →  photos-originals/<hex>/<UUID>.ext
  case "$path" in
    "$ORIG_ROOT"*) rel="${path#"$ORIG_ROOT"}" ;;
    *) log "skip (outside originals/): $path"; continue ;;
  esac
  key="$PREFIX/$rel"

  if grep -qxF "$rel" "$SEEDED"; then
    skip=$((skip+1))
  else
    rclone copyto "$path" ":s3:$BUCKET/$key" 2>/dev/null \
      || { log "upload FAILED: $key"; continue; }
    up=$((up+1))
  fi

  # Reclaim space: drop the local original once it's safely in Spaces.
  if [ "$EVICT" -eq 1 ]; then
    rm -f "$path"
  fi

  done=$((done+1))
  if [ $((done % 100)) -eq 0 ]; then
    log "progress: $done/$TOTAL (up=$up skip=$skip) free=$(free_gb)G"
  fi
done < "$MANIFEST"

log "done. processed=$done uploaded=$up skipped=$skip free=$(free_gb)G"
[ "$EVICT" -eq 1 ] && log "originals evicted locally — bring up the mount to serve them back."
