#!/bin/bash
# update-icons.sh — Replace Unraid Docker/VM icons with selfh.st CDN icons
# Part of the unraid-glass theme project
set -euo pipefail

# --- Configuration ---
CDN_BASE="https://cdn.jsdelivr.net/gh/selfhst/icons@main/png"
ICON_RAM="/usr/local/emhttp/state/plugins/dynamix.docker.manager/images"
ICON_PERSIST="/var/lib/docker/unraid/images"
VM_ICON_DIR="/usr/local/emhttp/plugins/dynamix.vm.manager/templates/images"
CONFIG_DIR="/boot/config/plugins/custom.css/assets"
VM_CONFIG="${CONFIG_DIR}/vm-icons.conf"
TMP_DIR="/tmp/glass-icons"
LABEL_NAME="glass.icon"

FORCE=false
QUIET=false
DRY_RUN=false
DOCKER_ONLY=false
VM_ONLY=false

updated=0
skipped=0
failed=0

# --- Helpers ---
log() { $QUIET || echo "$@"; }
warn() { echo "WARN: $@" >&2; }

usage() {
  cat <<'EOF'
Usage: update-icons.sh [OPTIONS]

Replace Unraid Docker/VM icons with selfh.st CDN icons.

Options:
  --force        Re-download even if icon file already matches
  --quiet        Suppress output (for cron)
  --dry-run      Show what would be done without doing it
  --docker-only  Skip VM icon processing
  --vm-only      Skip Docker icon processing
  --help         Show this help
EOF
  exit 0
}

# --- Argument parsing ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    --force)       FORCE=true ;;
    --quiet)       QUIET=true ;;
    --dry-run)     DRY_RUN=true ;;
    --docker-only) DOCKER_ONLY=true ;;
    --vm-only)     VM_ONLY=true ;;
    --help)        usage ;;
    *) warn "Unknown option: $1"; usage ;;
  esac
  shift
done

# --- Auto-detection: try to match container name to selfh.st icon ---
cdn_exists() {
  curl -fsSL --head --max-time 5 "${CDN_BASE}/${1}.png" >/dev/null 2>&1
}

auto_detect_icon() {
  local container="$1"
  local name

  # Known mappings for containers that won't auto-match
  case "$(echo "$container" | tr '[:upper:]' '[:lower:]')" in
    autokuma)               echo "uptime-kuma"; return 0 ;;
    uptimekuma)             echo "uptime-kuma"; return 0 ;;
    uptime-kuma-api)        echo "uptime-kuma"; return 0 ;;
    homeassistant_inabox)   echo "home-assistant"; return 0 ;;
    homeassistant*)         echo "home-assistant"; return 0 ;;
    dockersocket)           echo "docker"; return 0 ;;
    pgadmin*)               echo "pgadmin"; return 0 ;;
    postgresql*)            echo "postgresql"; return 0 ;;
    woodpecker-agent)       echo "woodpecker-ci"; return 0 ;;
    woodpecker-server)      echo "woodpecker-ci"; return 0 ;;
    rustdesk-hbbr)          echo "rustdesk"; return 0 ;;
    rustdesk-hbbs)          echo "rustdesk"; return 0 ;;
    rustfs)                 echo "rustdesk"; return 0 ;;
    netbox-redis-cache)     echo "redis"; return 0 ;;
    netbox-worker)          echo "netbox"; return 0 ;;
    netdisco-backend)       echo "networking-toolbox"; return 0 ;;
    netdisco-web)           echo "networking-toolbox"; return 0 ;;
    libation)               echo "audible"; return 0 ;;
    diode-*)                echo "netbox"; return 0 ;;
    authentik-worker)       echo "authentik"; return 0 ;;
    adguardhome-sync)       echo "adguard-home"; return 0 ;;
    adguardhome)            echo "adguard-home"; return 0 ;;
    actual-budget)          echo "actual-budget"; return 0 ;;
    speedtest-tracker)      echo "speedtest-tracker"; return 0 ;;
    timemachine)            echo "apple"; return 0 ;;
    open-webui)             echo "open-webui"; return 0 ;;
    urbackup)               echo "networking-toolbox"; return 0 ;;
    netalertx)              echo "netalertx"; return 0 ;;
    seekandwatch)           echo "databasement"; return 0 ;;
    unmarr)                 echo "homarr"; return 0 ;;
    dockhand)               echo "docker"; return 0 ;;
  esac

  # Strategy 1: lowercase name directly
  name=$(echo "$container" | tr '[:upper:]' '[:lower:]')
  if cdn_exists "$name"; then echo "$name"; return 0; fi

  # Strategy 2: underscores to hyphens
  name=$(echo "$container" | tr '[:upper:]' '[:lower:]' | tr '_' '-')
  if cdn_exists "$name"; then echo "$name"; return 0; fi

  # Strategy 3: strip common suffixes
  name=$(echo "$container" | tr '[:upper:]' '[:lower:]' | sed -E 's/-(worker|agent|server|web|backend|api|cache|migrate|bootstrap|sync|ingress|ingester|reconciler)$//')
  if cdn_exists "$name"; then echo "$name"; return 0; fi

  return 1
}

# Resolve label value to a full URL
resolve_url() {
  local value="$1"
  if [[ "$value" == http* ]]; then
    echo "$value"
  else
    echo "${CDN_BASE}/${value}.png"
  fi
}

# Download icon to temp, validate, and copy to target paths
download_and_replace() {
  local name="$1"
  local url="$2"
  local target_ram="$3"
  local target_persist="$4"
  local tmp_file="${TMP_DIR}/${name}.png"

  if $DRY_RUN; then
    log "  [DRY-RUN] Would download: $url"
    log "  [DRY-RUN] Would replace: $target_ram"
    log "  [DRY-RUN] Would replace: $target_persist"
    ((updated++)) || true
    return 0
  fi

  # Download
  if ! curl -fsSL --max-time 15 -o "$tmp_file" "$url" 2>/dev/null; then
    warn "Download failed for $name: $url"
    ((failed++)) || true
    return 1
  fi

  # Validate non-empty
  if [ ! -s "$tmp_file" ]; then
    warn "Empty download for $name: $url"
    rm -f "$tmp_file"
    ((failed++)) || true
    return 1
  fi

  # Skip if identical (unless --force)
  if ! $FORCE && [ -f "$target_ram" ] && cmp -s "$tmp_file" "$target_ram"; then
    log "  [SKIP] $name — icon unchanged"
    rm -f "$tmp_file"
    ((skipped++)) || true
    return 0
  fi

  # Replace in both paths
  cp "$tmp_file" "$target_ram" 2>/dev/null || true
  if [ -n "$target_persist" ] && [ -d "$(dirname "$target_persist")" ]; then
    cp "$tmp_file" "$target_persist" 2>/dev/null || true
  fi
  rm -f "$tmp_file"

  log "  [OK] $name — icon updated"
  ((updated++)) || true
  return 0
}

# --- Main ---
mkdir -p "$TMP_DIR"
trap 'rm -rf "$TMP_DIR"' EXIT

# === Docker containers ===
if ! $VM_ONLY; then
  log "=== Docker Containers ==="

  if ! docker info >/dev/null 2>&1; then
    warn "Docker is not running, skipping containers"
  else
    while IFS= read -r container; do
      [ -z "$container" ] && continue
      log "Processing: $container"

      # Read glass.icon label
      label=$(docker inspect --format "{{index .Config.Labels \"${LABEL_NAME}\"}}" "$container" 2>/dev/null || echo "")
      label="${label#<no value>}"  # docker inspect returns "<no value>" if missing

      if [ -n "$label" ]; then
        icon_url=$(resolve_url "$label")
        log "  Label: $label → $icon_url"
      else
        # Auto-detect
        if icon_name=$(auto_detect_icon "$container"); then
          icon_url=$(resolve_url "$icon_name")
          log "  Auto-detect: $icon_name → $icon_url"
        else
          log "  [SKIP] No label, no auto-match"
          ((skipped++)) || true
          continue
        fi
      fi

      download_and_replace \
        "$container" \
        "$icon_url" \
        "${ICON_RAM}/${container}-icon.png" \
        "${ICON_PERSIST}/${container}-icon.png" || true

    done < <(docker ps -a --format '{{.Names}}')
  fi
fi

# === Virtual Machines ===
if ! $DOCKER_ONLY; then
  log ""
  log "=== Virtual Machines ==="

  if [ ! -f "$VM_CONFIG" ]; then
    log "No VM config found at $VM_CONFIG, skipping VMs"
  elif ! command -v virsh >/dev/null 2>&1; then
    warn "virsh not found, skipping VMs"
  else
    while IFS='=' read -r vm_name icon_value; do
      # Skip comments and empty lines
      [[ "$vm_name" =~ ^[[:space:]]*# ]] && continue
      [ -z "$vm_name" ] && continue
      vm_name=$(echo "$vm_name" | xargs)  # trim whitespace
      icon_value=$(echo "$icon_value" | xargs)
      [ -z "$icon_value" ] && continue

      log "Processing VM: $vm_name"

      # Get current icon filename from VM XML
      icon_file=$(virsh dumpxml "$vm_name" 2>/dev/null | grep -oP 'icon="\K[^"]+' || echo "")
      if [ -z "$icon_file" ]; then
        warn "Could not read icon for VM '$vm_name'"
        ((failed++)) || true
        continue
      fi

      icon_url=$(resolve_url "$icon_value")
      log "  Config: $icon_value → $icon_url"

      download_and_replace \
        "vm-${vm_name// /-}" \
        "$icon_url" \
        "${VM_ICON_DIR}/${icon_file}" \
        "" || true

    done < "$VM_CONFIG"
  fi
fi

# === Summary ===
log ""
log "=== Done ==="
log "Updated: $updated | Skipped: $skipped | Failed: $failed"
