#!/bin/zsh
#
# Gnommo Backup Utility
# Syncs project files to an external drive using rsync
#
# Usage: ./backup.sh <mode> <drive> [options]
#
# Modes:
#   small  - Keynotes, images, metadata, code (excludes large media)
#   big    - Large video/audio files only (for offloading)
#   all    - Complete mirror of entire project
#
# Drives:
#   lacie      - /Volumes/LaCie Jens/gnommo
#   gnommodisk - /Volumes/gnommodisk/gnommo
#   status     - Show backup status for all drives
#
# Options:
#   --dry-run    Show what would be transferred without copying
#   --delete     Delete files on destination that don't exist in source
#   --progress   Show detailed transfer progress (default: on)
#

set -e

# Configuration
PROJECT_DIR="/Users/jenstandstad/Projects/gnommo"
BACKUP_JSON="$PROJECT_DIR/backup.json"
BIG_FILE_SIZE="100M"

# Known drives (name -> mount path)
typeset -A KNOWN_DRIVES
KNOWN_DRIVES=(
    lacie "/Volumes/LaCie Jens"
    gnommodisk "/Volumes/gnommodisk"
)

# Big file extensions (video/audio that tend to be large)
BIG_EXTENSIONS=("mov" "mp4" "m4v" "avi" "mkv" "aifc" "aiff" "wav")

# Initialize backup.json if it doesn't exist
init_backup_json() {
    if [[ ! -f "$BACKUP_JSON" ]]; then
        cat > "$BACKUP_JSON" << 'EOF'
{
  "drives": {}
}
EOF
    fi
}

# Update backup.json using Python (reliable JSON handling)
update_backup_json() {
    local drive_name="$1"
    local backup_mode="$2"
    local backup_status="$3"  # "started" or "completed"
    local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

    python3 << PYTHON
import json
import os

backup_file = "$BACKUP_JSON"
drive_name = "$drive_name"
mode = "$backup_mode"
status = "$backup_status"
timestamp = "$timestamp"

# Load existing data
if os.path.exists(backup_file):
    with open(backup_file, 'r') as f:
        data = json.load(f)
else:
    data = {"drives": {}}

# Ensure drive entry exists
if drive_name not in data["drives"]:
    data["drives"][drive_name] = {
        "mount_path": "${KNOWN_DRIVES[$drive_name]:-$DESTINATION}",
        "backups": {}
    }

# Ensure mode entry exists
if mode not in data["drives"][drive_name]["backups"]:
    data["drives"][drive_name]["backups"][mode] = {}

# Update based on status
backup_entry = data["drives"][drive_name]["backups"][mode]
if status == "started":
    backup_entry["last_attempt"] = timestamp
    backup_entry["last_status"] = "in_progress"
elif status == "completed":
    backup_entry["last_completed"] = timestamp
    backup_entry["last_status"] = "success"
elif status == "failed":
    backup_entry["last_status"] = "failed"

# Write back
with open(backup_file, 'w') as f:
    json.dump(data, f, indent=2)
PYTHON
}

# Show backup status
show_status() {
    echo "========================================"
    echo "Gnommo Backup Status"
    echo "========================================"

    if [[ ! -f "$BACKUP_JSON" ]]; then
        echo "No backups recorded yet."
        exit 0
    fi

    python3 << 'PYTHON'
import json
import os
from datetime import datetime

backup_file = os.environ.get('BACKUP_JSON', 'backup.json')
known_drives = {"lacie": "/Volumes/LaCie Jens", "gnommodisk": "/Volumes/gnommodisk"}

with open(backup_file, 'r') as f:
    data = json.load(f)

for drive_name, drive_info in data.get("drives", {}).items():
    mount_path = drive_info.get("mount_path", "unknown")
    mounted = "CONNECTED" if os.path.exists(mount_path) else "not connected"

    print(f"\n{drive_name} ({mounted})")
    print(f"  Path: {mount_path}")

    backups = drive_info.get("backups", {})
    if not backups:
        print("  No backups recorded")
        continue

    for mode, info in backups.items():
        status = info.get("last_status", "unknown")
        completed = info.get("last_completed", "never")
        attempt = info.get("last_attempt", "never")

        # Format the completed time nicely
        if completed != "never":
            try:
                dt = datetime.fromisoformat(completed.replace('Z', '+00:00'))
                completed = dt.strftime("%Y-%m-%d %H:%M UTC")
            except:
                pass

        status_icon = "✓" if status == "success" else "⋯" if status == "in_progress" else "✗"
        print(f"  {mode}: {status_icon} {completed}")

print()
PYTHON

    echo "========================================"
}

usage() {
    cat << EOF
Gnommo Backup Utility

Usage: $(basename "$0") <mode> <drive> [options]

Modes:
  small     Sync small files only: Keynotes, images, JSON, code, manuscripts
            Excludes: .mov, .mp4, .aifc, and other large media files

  big       Sync large files only: video and audio media files
            Useful for offloading to free up local space

  all       Full mirror of the entire gnommo project

  status    Show backup status for all known drives

Drives:
  lacie       /Volumes/LaCie Jens/gnommo
  gnommodisk  /Volumes/gnommodisk/gnommo
  <path>      Or specify a custom path

Options:
  --dry-run     Preview what would be transferred (no actual copying)
  --delete      Remove files on destination that no longer exist in source
  --no-progress Disable progress display
  --help        Show this help message

Examples:
  $(basename "$0") status
  $(basename "$0") small lacie
  $(basename "$0") big gnommodisk --delete
  $(basename "$0") all lacie --dry-run

EOF
    exit 0
}

# Parse arguments
MODE=""
DRIVE=""
DESTINATION=""
DRY_RUN=""
DELETE=""
PROGRESS="--progress"

while [[ $# -gt 0 ]]; do
    case "$1" in
        small|big|all)
            MODE="$1"
            shift
            ;;
        status)
            export BACKUP_JSON
            show_status
            exit 0
            ;;
        lacie|gnommodisk)
            DRIVE="$1"
            DESTINATION="${KNOWN_DRIVES[$1]}/gnommo"
            shift
            ;;
        --dry-run)
            DRY_RUN="--dry-run"
            shift
            ;;
        --delete)
            DELETE="--delete"
            shift
            ;;
        --no-progress)
            PROGRESS=""
            shift
            ;;
        --help|-h)
            usage
            ;;
        -*)
            echo "Unknown option: $1"
            usage
            ;;
        *)
            if [[ -z "$DESTINATION" ]]; then
                DRIVE="custom"
                DESTINATION="$1"
            fi
            shift
            ;;
    esac
done

# Handle status command
if [[ "$MODE" == "status" ]]; then
    show_status
    exit 0
fi

# Validate arguments
if [[ -z "$MODE" ]]; then
    echo "Error: Mode is required (small, big, all, or status)"
    echo ""
    usage
fi

if [[ -z "$DESTINATION" ]]; then
    echo "Error: Drive or destination path is required"
    echo ""
    usage
fi

# Check if drive is mounted (get the volume path, handling spaces)
MOUNT_PATH="${DESTINATION%/gnommo}"
if [[ ! -d "$MOUNT_PATH" ]]; then
    echo "Error: Drive not mounted at: $MOUNT_PATH"
    echo ""
    echo "Available volumes:"
    ls /Volumes/ 2>/dev/null | sed 's/^/  /'
    exit 1
fi

# Create destination directory if needed
mkdir -p "$DESTINATION"

# Initialize backup tracking
init_backup_json

# Build rsync command
RSYNC_OPTS="-avh"
[[ -n "$PROGRESS" ]] && RSYNC_OPTS="$RSYNC_OPTS --progress"
[[ -n "$DRY_RUN" ]] && RSYNC_OPTS="$RSYNC_OPTS --dry-run"
[[ -n "$DELETE" ]] && RSYNC_OPTS="$RSYNC_OPTS --delete"

# Always exclude these
EXCLUDE_ALWAYS=(
    ".DS_Store"
    "__pycache__"
    "*.pyc"
    ".git"
    ".env"
    "*.egg-info"
    ".venv"
    "venv"
    "node_modules"
)

# Build exclusion patterns for big files
build_big_excludes() {
    local excludes=""
    for ext in "${BIG_EXTENSIONS[@]}"; do
        excludes="$excludes --exclude='*.$ext'"
    done
    echo "$excludes"
}

# Build inclusion patterns for big files only
build_big_includes() {
    local includes=""
    for ext in "${BIG_EXTENSIONS[@]}"; do
        includes="$includes --include='*.$ext'"
    done
    echo "$includes"
}

# Build common exclusions
build_common_excludes() {
    local excludes=""
    for pattern in "${EXCLUDE_ALWAYS[@]}"; do
        excludes="$excludes --exclude='$pattern'"
    done
    echo "$excludes"
}

echo "========================================"
echo "Gnommo Backup Utility"
echo "========================================"
echo "Mode:        $MODE"
echo "Drive:       $DRIVE"
echo "Source:      $PROJECT_DIR"
echo "Destination: $DESTINATION"
[[ -n "$DRY_RUN" ]] && echo "DRY RUN:     Yes (no files will be copied)"
[[ -n "$DELETE" ]] && echo "Delete:      Yes (will remove orphaned files)"
echo "========================================"
echo ""

# Record backup attempt (skip for dry-run)
if [[ -z "$DRY_RUN" ]]; then
    update_backup_json "$DRIVE" "$MODE" "started"
fi

# Track success
BACKUP_SUCCESS=false

run_backup() {
    case "$MODE" in
        small)
            echo "Syncing SMALL files (excluding large media)..."
            echo "Excludes: ${BIG_EXTENSIONS[*]}"
            echo ""

            EXCLUDES=$(build_common_excludes)
            BIG_EXCLUDES=$(build_big_excludes)

            eval rsync $RSYNC_OPTS $EXCLUDES $BIG_EXCLUDES "'$PROJECT_DIR/'" "'$DESTINATION/'"
            ;;

        big)
            echo "Syncing BIG files only (large media)..."
            echo "Includes: ${BIG_EXTENSIONS[*]}"
            echo ""

            EXCLUDES=$(build_common_excludes)
            INCLUDES="--include='*/' $(build_big_includes)"

            eval rsync $RSYNC_OPTS $EXCLUDES $INCLUDES --exclude="'*'" "'$PROJECT_DIR/'" "'$DESTINATION/'"
            ;;

        all)
            echo "Syncing ALL files (complete mirror)..."
            echo ""

            EXCLUDES=$(build_common_excludes)

            eval rsync $RSYNC_OPTS $EXCLUDES "'$PROJECT_DIR/'" "'$DESTINATION/'"
            ;;
    esac
}

# Run backup and track result
# Exit codes: 0=success, 23=partial transfer (files changed during sync, usually OK), 24=vanished files
run_backup && BACKUP_SUCCESS=true || {
    local exit_code=$?
    if [[ $exit_code -eq 23 || $exit_code -eq 24 ]]; then
        echo "Note: Some files changed during transfer (rsync exit $exit_code) - backup completed"
        BACKUP_SUCCESS=true
    fi
}

echo ""
echo "========================================"
if [[ -n "$DRY_RUN" ]]; then
    echo "DRY RUN complete. No files were copied."
else
    if [[ "$BACKUP_SUCCESS" == true ]]; then
        update_backup_json "$DRIVE" "$MODE" "completed"
        echo "Backup complete!"
    else
        update_backup_json "$DRIVE" "$MODE" "failed"
        echo "Backup FAILED!"
    fi
fi
echo "========================================"
