#!/bin/sh
# zfs-pool-migrate.sh — Pool migration wizard
#
# Handles three migration methods:
#   1. Disk-by-disk replacement (4 bays needed)
#   2. New pool + transfer (6 bays needed, or USB enclosures)
#   3. Backup + restore (external storage needed)

set -e

POOL_NAME="clawdie"
LOG="/var/log/clawdie-migrate.log"

. "/usr/local/share/clawdie-iso/build.cfg"

dialog() { bsddialog --backtitle "Clawdie Pool Migration" "$@" ; }
die() { echo "ERROR: $1" >&2; exit 1; }
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG"; }

get_disk_info() {
    local disk=$1
    local info=""
    
    info+="DEVICE:  $disk\n"
    
    local model=$(camcontrol inquiry $disk 2>/dev/null | head -1)
    [ -n "$model" ] && info+="MODEL:   $model\n"
    
    local serial=$(camcontrol identify $disk 2>/dev/null | grep -i 'serial number' | cut -d: -f2 | tr -d ' ')
    [ -n "$serial" ] && info+="SERIAL: $serial\n"
    
    local size_bytes=$(diskinfo $disk 2>/dev/null | awk '{print $3}')
    if [ -n "$size_bytes" ]; then
        local size_gb=$((size_bytes / 1073741824))
        info+="SIZE:   ${size_gb} GB\n"
    fi
    
    local temp=$(smartctl -A $disk 2>/dev/null | grep -i temperature | awk '{print $10}')
    [ -n "$temp" ] && info+="TEMP:   ${temp}°C\n"
    
    local health=$(smartctl -H $disk 2>/dev/null | grep -E '^SMART overall-health' | awk '{print $NF}')
    [ -n "$health" ] && info+="HEALTH: $health\n"
    
    local slot=$(sesutil map 2>/dev/null | grep "^$disk" | awk '{print $3}')
    [ -n "$slot" ] && info+="SLOT:   $slot\n"
    
    echo "$info"
}

get_pool_status() {
    local status=$(zpool status $POOL_NAME 2>/dev/null)
    echo "$status"
}

get_pool_usage() {
    local used=$(zfs list -Hp -o used $POOL_NAME 2>/dev/null)
    local avail=$(zfs list -Hp -o avail $POOL_NAME 2>/dev/null)
    local total=$((used + avail))
    local used_gb=$((used / 1073741824))
    local total_gb=$((total / 1073741824))
    local percent=$((used * 100 / total))
    
    echo "USED:   ${used_gb} GB / ${total_gb} GB (${percent}%)"
}

count_disks_in_pool() {
    zpool status $POOL_NAME 2>/dev/null | grep -E '^\s+(ada|da|nvd|nda)' | wc -l
}

get_pool_disks() {
    zpool status $POOL_NAME 2>/dev/null | grep -E '^\s+(ada|da|nvd|nda)' | awk '{print $1}'
}

get_available_disks() {
    local pool_disks=$(get_pool_disks | sort -u)
    local all_disks=$(camcontrol devlist 2>/dev/null | grep -oE '(ada|da|nvd|nda)' | awk '{print $NF}' | sort -u)
    
    for d in $all_disks; do
        if ! echo "$pool_disks" | grep -qw "$d"; then
            echo "$d"
        fi
    done
}

format_size() {
    local bytes=$1
    if [ $bytes -ge 1099511627776 ]; then
        echo "$((bytes / 1099511627776)) TB"
    elif [ $bytes -ge 1073741824 ]; then
        echo "$((bytes / 1073741824)) GB"
    elif [ $bytes -ge 1048576 ]; then
        echo "$((bytes / 1048576)) MB"
    else
        echo "$bytes bytes"
    fi
}

method_disk_by_disk() {
    log "Starting disk-by-disk migration"
    
    local pool_disks=$(get_pool_disks)
    local disk_count=$(echo "$pool_disks" | wc -l)
    local available=$(get_available_disks)
    
    if [ -z "$available" ]; then
        dialog --msgbox \
            "\nNo replacement disks available.\n\nConnect replacement disks and restart." \
            10 50
        return 1
    fi
    
    local step=1
    local total_steps=$disk_count
    
    while [ $step -le $total_steps ]; do
        local current_disk=$(echo "$pool_disks" | sed -n "${step}p")
        local disk_info=$(get_disk_info $current_disk)
        
        local replacement=$(dialog --menu \
            "Step ${step} of ${total_steps}: Replace ${current_disk}\n\n${disk_info}\n\nSelect replacement disk:" \
            20 70 10 \
            $(echo "$available" | while read -r disk; do echo "$disk \"$(get_disk_info $disk | head -1)\""; done) \
            3>&1 1>&2 2>&3)
        
        [ $? -ne 0 ] && return 1
        
        local replacement_size=$(diskinfo $replacement 2>/dev/null | awk '{print $3}')
        local current_size=$(diskinfo $current_disk 2>/dev/null | awk '{print $3}')
        
        if [ $replacement_size -lt $current_size ]; then
            dialog --msgbox \
                "\nReplacement disk is smaller than current disk.\n\nReplacement: $(format_size $replacement_size)\nCurrent: $(format_size $current_size)\n\nUse a disk of equal or larger size." \
                12 60
            continue
        fi
        
        local confirm=$(dialog --yesno \
            "\nConfirm replacement:\n\n  ${current_disk} → ${replacement}\n\nThis will take 4-8 hours.\nPool remains usable during resilver.\n\nContinue?" \
            12 60)
        
        [ "$confirm" != "yes" ] && continue
        
        log "Replacing $current_disk with $replacement"
        
        dialog --infobox "Starting resilver...\nThis screen will update periodically." 6 50
        
        zpool replace $POOL_NAME $current_disk $replacement 2>&1 | tee -a "$LOG"
        
        local progress=0
        while true; do
            local status=$(zpool status $POOL_NAME 2>/dev/null)
            if echo "$status" | grep -q "resilver completed"; then
                break
            fi
            
            local scan_line=$(echo "$status" | grep -A1 "scan:")
            if echo "$scan_line" | grep -qE '[0-9]+%'; then
                progress=$(echo "$scan_line" | grep -oE '[0-9]+' | head -1 | tr -d '%')
                dialog --infobox "Resilver in progress: ${progress}%\n\nReplacing: ${current_disk} → ${replacement}\n\nDo not power off." 6 50
            fi
            
            sleep 30
        done
        
        log "Resilver completed for $current_disk → $replacement"
        
        pool_disks=$(echo "$pool_disks" | sed "s/${current_disk}/${replacement}/")
        
        step=$((step + 1))
    done
    
    dialog --msgbox \
        "\nDisk-by-disk migration complete!\n\nAll disks have been replaced.\nPool remains redundant throughout the process.\n\nConsider running:\n  zpool scrub $POOL_NAME\n\nto verify data integrity." \
        15 60
    
    log "Disk-by-disk migration completed successfully"
    return 0
}

method_new_pool_transfer() {
    log "Starting new pool + transfer migration"
    
    local available=$(get_available_disks | sort -u)
    local available_count=$(echo "$available" | wc -l)
    
    if [ $available_count -lt 3 ]; then
        local use_usb=$(dialog --yesno \
            "\nNot enough internal disks for new pool.\n\nFound: ${available_count} available\nNeeded: 3 disks\n\nUse USB enclosures for additional disks?" \
            12 60)
        
        if [ "$use_usb" != "yes" ]; then
            dialog --msgbox \
                "\nCannot proceed.\n\nConnect additional disks (USB enclosures work) and restart.\n\nAlternatively, use disk-by-disk method (4 bays) or backup/restore (external storage)." \
                12 60
            return 1
        fi
    fi
    
    local selected=$(dialog --checklist \
        "Select 3 disks for new migration pool:\n\nThese disks will be ERASED." \
        20 70 10 \
        $(echo "$available" | while read -r disk; do echo "$disk \"$(get_disk_info $disk | grep MODEL | cut -d: -f2-)\" off"; done) \
        3>&1 1>&2 2>&3)
    
    [ $? -ne 0 ] && return 1
    
    local selected_count=$(echo "$selected" | wc -w)
    if [ $selected_count -ne 3 ]; then
        dialog --msgbox "\nPlease select exactly 3 disks.\n\nSelected: ${selected_count}" 10 50
        return 1
    fi
    
    local raid_type=$(dialog --radiolist \
        "RAID level for new pool:" 12 60 3 \
        "raidz1" "3 disks, 1 fault tolerance (recommended)" on \
        "raidz2" "4+ disks, 2 fault tolerance" off \
        "mirror" "2 disks, 1 fault tolerance, faster reads" off \
        3>&1 1>&2 2>&3)
    
    [ $? -ne 0 ] && return 1
    
    local confirm=$(dialog --yesno \
        "\nCreate new pool and transfer data?\n\nNew pool: clawdie-new\nRAID: ${raid_type}\nDisks: ${selected}\n\n⚠ All data on selected disks will be DESTROYED.\n\nTransfer time: ~4-8 hours" \
        15 70)
    
    [ "$confirm" != "yes" ] && return 1
    
    log "Creating new pool clawdie-new with disks: $selected"
    
    local disk_args=""
    for disk in $selected; do
        disk_args="$disk_args /dev/$disk"
    done
    
    zpool create clawdie-new $raid_type $disk_args 2>&1 | tee -a "$LOG"
    
    [ $? -ne 0 ] && die "Failed to create new pool"
    
    dialog --infobox "Transferring data...\nThis will take 4-8 hours.\nProgress shown periodically." 6 60
    
    log "Starting zfs send/recv from $POOL_NAME to clawdie-new"
    
    local progress_file="/tmp/zfs-transfer-progress"
    
    zfs snapshot -r $POOL_NAME@migration-transfer 2>&1 | tee -a "$LOG"
    
    ( zfs send -Rv $POOL_NAME@migration-transfer 2>&1 | tee "$progress_file" | \
      pv -l -F -p -b -t -r -i > "$progress_file.stats" 2>/dev/null | \
      zfs recv -v clawdie-new 2>&1 | tee -a "$LOG" ) || \
    zfs send -R $POOL_NAME@migration-transfer 2>&1 | zfs recv -F clawdie-new 2>&1 | tee -a "$LOG"
    
    [ $? -ne 0 ] && die "Transfer failed"
    
    log "Transfer completed"
    
    local verify=$(dialog --yesno \
        "\nData transfer complete!\n\nVerify data integrity? (recommended)\nThis compares source and destination." \
        10 60)
    
    if [ "$verify" = "yes" ]; then
        dialog --infobox "Verifying data integrity..." 5 50
        zfs diff $POOL_NAME@migration-transfer clawdie-new 2>&1 | tee -a "$LOG"
    fi
    
    local destroy_old=$(dialog --yesno \
        "\nMigration complete!\n\nOld pool '$POOL_NAME' is still intact.\n\nDestroy old pool now?\n(Recommended: keep old disks as backup for 1-2 weeks)" \
        12 70)
    
    if [ "$destroy_old" = "yes" ]; then
        log "Destroying old pool $POOL_NAME"
        zpool export $POOL_NAME 2>&1 | tee -a "$LOG"
        dialog --msgbox \
            "\nOld pool exported.\n\nOld disks can now be removed.\n\nNew pool 'clawdie-new' needs to be renamed:\n\n  zpool export clawdie-new\n  zpool import clawdie-new clawdie" \
            12 60
    else
        dialog --msgbox \
            "\nBoth pools exist:\n  - $POOL_NAME (old)\n  - clawdie-new (new)\n\nTo complete migration:\n  1. Boot from USB\n  2. Export old pool\n  3. Rename new pool:\n     zpool export clawdie-new\n     zpool import clawdie-new clawdie\n  4. Reboot" \
            15 70
    fi
    
    log "New pool + transfer migration completed"
    return 0
}

method_backup_restore() {
    log "Starting backup + restore migration"
    
    local backup_targets=""
    
    backup_targets+="local External USB drive\n"
    backup_targets+="network Network (SSH/SCP)\n"
    backup_targets+="zfs Another ZFS pool\n"
    
    local dest_type=$(dialog --menu \
        "Select backup destination:" 12 60 3 \
        $backup_targets \
        3>&1 1>&2 2>&3)
    
    [ $? -ne 0 ] && return 1
    
    local dest_path=""
    local dest_size=0
    
    case "$dest_type" in
        "local")
            local usb_disks=$(camcontrol devlist 2>/dev/null | grep -oE '(da|ada)[0-9]+' | sort -u)
            
            if [ -z "$usb_disks" ]; then
                dialog --msgbox "\nNo external drives detected.\n\nConnect external drive and restart." 10 50
                return 1
            fi
            
            dest_path=$(dialog --menu \
                "Select external drive:" 12 60 5 \
                $(echo "$usb_disks" | while read -r disk; do echo "$disk \"$(get_disk_info $disk | grep MODEL | cut -d: -f2-)\""; done) \
                3>&1 1>&2 2>&3)
            
            [ $? -ne 0 ] && return 1
            
            dest_path="/dev/$dest_path"
            dest_size=$(diskinfo $dest_path 2>/dev/null | awk '{print $3}')
            ;;
            
        "network")
            dest_path=$(dialog --inputbox \
                "Network destination (user@host:/path):" 8 60 \
                "user@backup-server:/backups/clawdie" \
                3>&1 1>&2 2>&3)
            [ $? -ne 0 ] && return 1
            ;;
            
        "zfs")
            local other_pools=$(zpool import 2>/dev/null | grep "pool:" | awk '{print $2}' | grep -v "^$POOL_NAME$")
            
            if [ -z "$other_pools" ]; then
                dialog --msgbox "\nNo other pools available.\n\nCreate a backup pool first." 10 50
                return 1
            fi
            
            dest_path=$(dialog --menu \
                "Select backup pool:" 10 60 3 \
                $(echo "$other_pools" | while read -r pool; do echo "$pool \"ZFS pool\""; done) \
                3>&1 1>&2 2>&3)
            [ $? -ne 0 ] && return 1
            ;;
    esac
    
    local pool_used=$(zfs list -Hp -o used $POOL_NAME 2>/dev/null)
    local pool_used_gb=$((pool_used / 1073741824))
    
    local confirm=$(dialog --yesno \
        "\nBackup configuration:\n\n  Source: $POOL_NAME (${pool_used_gb} GB)\n  Destination: $dest_path\n\nTime: ~4-8 hours\n\nStart backup?" \
        12 60)
    
    [ "$confirm" != "yes" ] && return 1
    
    log "Starting backup to $dest_path"
    
    dialog --infobox "Creating backup...\nThis will take 4-8 hours." 6 50
    
    case "$dest_type" in
        "local")
            local mount_point="/mnt/backup"
            mkdir -p "$mount_point"
            newfs "$dest_path" 2>&1 | tee -a "$LOG" || mount -t ufs "$dest_path" "$mount_point" 2>&1 | tee -a "$LOG"
            zfs send -R $POOL_NAME@backup-$(date +%Y.%m.%d) > "$mount_point/clawdie-backup.zfs" 2>&1 | tee -a "$LOG"
            umount "$mount_point"
            ;;
        "network")
            zfs send -R $POOL_NAME@backup-$(date +%Y.%m.%d) | ssh "$dest_path" "cat > clawdie-backup.zfs" 2>&1 | tee -a "$LOG"
            ;;
        "zfs")
            zfs send -R $POOL_NAME@backup-$(date +%Y.%m.%d) | zfs recv "${dest_path}/clawdie-backup" 2>&1 | tee -a "$LOG"
            ;;
    esac
    
    [ $? -ne 0 ] && die "Backup failed"
    
    log "Backup completed"
    
    dialog --msgbox \
        "\nBackup complete!\n\nNow:\n  1. Power off\n  2. Replace old disks with new disks\n  3. Boot USB again\n  4. Select 'Restore from backup'\n\nOld pool will be destroyed." \
        12 60
    
    log "Backup + restore migration - backup phase completed"
    return 0
}

main() {
    log "Pool migration wizard started"
    
    kldload zfs 2>/dev/null || true
    
    if ! zpool list $POOL_NAME >/dev/null 2>&1; then
        dialog --msgbox \
            "\nPool '$POOL_NAME' not found.\n\nMigration requires an existing pool." \
            10 50
        return 1
    fi
    
    local pool_status=$(get_pool_status)
    local pool_usage=$(get_pool_usage)
    local disk_count=$(count_disks_in_pool)
    
    local method=$(dialog --radiolist \
        "Pool Migration\n\nPool: $POOL_NAME\n${pool_usage}\nDisks: ${disk_count}\n\nSelect migration method:" \
        18 70 3 \
        "disk-by-disk" "Replace one disk at a time (4 bays)" on \
        "new-pool" "Create new pool + transfer (6 bays)" off \
        "backup-restore" "Backup + restore (external storage)" off \
        3>&1 1>&2 2>&3)
    
    [ $? -ne 0 ] && return 1
    
    case "$method" in
        "disk-by-disk")
            method_disk_by_disk
            ;;
        "new-pool")
            method_new_pool_transfer
            ;;
        "backup-restore")
            method_backup_restore
            ;;
        *)
            die "Unknown method: $method"
            ;;
    esac
}

main "$@"
