#!/usr/bin/env bash
# check-docker-gpu.sh — Diagnostic and optional setup helper for NVIDIA Docker GPU access.
#
# Default mode is READ-ONLY — does not install packages, modify config, or restart Docker.
# The Odysseus app never calls this script automatically.
#
# USAGE
#   scripts/check-docker-gpu.sh                              # read-only diagnostics (default)
#   scripts/check-docker-gpu.sh --enable-nvidia-overlay     # also write COMPOSE_FILE to .env
#   scripts/check-docker-gpu.sh --print-install-commands    # show OS-specific commands, don't run
#   scripts/check-docker-gpu.sh --install-nvidia-toolkit    # install toolkit (Ubuntu/Debian only)
#   scripts/check-docker-gpu.sh --install-nvidia-toolkit --enable-nvidia-overlay
#   scripts/check-docker-gpu.sh --install-nvidia-toolkit --enable-nvidia-overlay --yes
#   scripts/check-docker-gpu.sh --help

MODE="check"
OPT_YES=0
OPT_ENABLE_OVERLAY=0
_GPU_PASSTHROUGH_OK=0

# ─── output helpers ──────────────────────────────────────────────────────────

PASS=0
FAIL=0

_pass() { printf '\033[32m[PASS]\033[0m %s\n' "$*"; PASS=$((PASS + 1)); }
_fail() { printf '\033[31m[FAIL]\033[0m %s\n' "$*"; FAIL=$((FAIL + 1)); }
_info() { printf '\033[34m[INFO]\033[0m %s\n' "$*"; }
_warn() { printf '\033[33m[WARN]\033[0m %s\n' "$*"; }
_step() { printf '\033[36m[STEP]\033[0m %s\n' "$*"; }

_confirm() {
    printf '%s [y/N] ' "$1"
    read -r _ans
    case "${_ans}" in
        [Yy]|[Yy][Ee][Ss]) return 0 ;;
        *) return 1 ;;
    esac
}

# ─── paths ───────────────────────────────────────────────────────────────────

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"

# ─── arg parsing ─────────────────────────────────────────────────────────────

_usage() {
    cat <<'USAGE'
Usage: scripts/check-docker-gpu.sh [OPTIONS]

Read-only diagnostic (default — safe to run at any time, installs nothing):
  (no flags)                    Check host nvidia-smi, Docker daemon, and Docker
                                GPU passthrough. Prints PASS/FAIL and next steps.

Informational:
  --print-install-commands      Detect the OS and print recommended NVIDIA
                                Container Toolkit commands without running them.
                                Inspect these before deciding to install.
  --help                        Show this help.

Opt-in .env update (requires .env or .env.example in the repo root):
  --enable-nvidia-overlay       Write COMPOSE_FILE=docker-compose.yml:docker/gpu.nvidia.yml
                                into .env. Creates a timestamped backup first.
                                Blocked if GPU passthrough is not working — fix
                                passthrough first, then re-run. --yes does not
                                override this gate.
                                Never edits .env unless this flag is passed.

Opt-in install (Ubuntu/Debian only, requires sudo):
  --install-nvidia-toolkit      Add NVIDIA's apt repository, install
                                nvidia-container-toolkit, configure the Docker
                                runtime, and optionally restart Docker.
                                Shows all commands and prompts before any
                                privileged action.
  --yes                         Skip confirmation prompts (for use with
                                --install-nvidia-toolkit and/or
                                --enable-nvidia-overlay in automated setups).

Examples:
  # Diagnose GPU passthrough before enabling the NVIDIA compose overlay:
  scripts/check-docker-gpu.sh

  # See what install commands apply to this system without running them:
  scripts/check-docker-gpu.sh --print-install-commands

  # Diagnose and automatically update .env with the NVIDIA overlay:
  scripts/check-docker-gpu.sh --enable-nvidia-overlay

  # Install toolkit interactively, then enable the overlay if it works:
  scripts/check-docker-gpu.sh --install-nvidia-toolkit --enable-nvidia-overlay

  # Full assisted setup without prompts (automated/CI use):
  scripts/check-docker-gpu.sh --install-nvidia-toolkit --enable-nvidia-overlay --yes

After a successful setup, start Odysseus:
  docker compose up -d --build

Full guide: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
USAGE
}

for _arg in "$@"; do
    case "${_arg}" in
        --help|-h)
            _usage
            exit 0
            ;;
        --print-install-commands)
            MODE="print"
            ;;
        --install-nvidia-toolkit)
            MODE="install"
            ;;
        --enable-nvidia-overlay)
            OPT_ENABLE_OVERLAY=1
            ;;
        --yes|-y)
            OPT_YES=1
            ;;
        *)
            printf 'Unknown option: %s\n\n' "${_arg}" >&2
            _usage >&2
            exit 1
            ;;
    esac
done

# ─── OS/distro detection ─────────────────────────────────────────────────────

DISTRO_ID=""
DISTRO_LIKE=""
DISTRO_VERSION=""
DISTRO_ARCH="$(uname -m 2>/dev/null || echo unknown)"

if [ -f /etc/os-release ]; then
    DISTRO_ID="$(grep '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"')"
    DISTRO_LIKE="$(grep '^ID_LIKE=' /etc/os-release | cut -d= -f2 | tr -d '"')"
    DISTRO_VERSION="$(grep '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '"')"
fi

_is_debian_family() {
    case "${DISTRO_ID}" in
        ubuntu|debian|linuxmint|pop|elementary) return 0 ;;
    esac
    # ID_LIKE can be a space-separated list, e.g. "ubuntu debian"
    case " ${DISTRO_LIKE} " in
        *" debian "*|*" ubuntu "*) return 0 ;;
    esac
    return 1
}

_distro_label() {
    if [ -n "${DISTRO_ID}" ]; then
        printf '%s%s (%s)' \
            "${DISTRO_ID}" \
            "${DISTRO_VERSION:+ ${DISTRO_VERSION}}" \
            "${DISTRO_ARCH}"
    else
        printf 'unknown Linux (%s)' "${DISTRO_ARCH}"
    fi
}

# ─── Ubuntu/Debian install command text ──────────────────────────────────────
# Printed both by --print-install-commands and shown before --install runs.

_debian_install_steps() {
    cat <<'STEPS'

  # 1. Install prerequisites
  sudo apt-get update
  sudo apt-get install -y curl gpg

  # 2. Add NVIDIA's signing key
  curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
    | sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg

  # 3. Add NVIDIA's apt repository
  curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
    | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
    | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

  # 4. Install the toolkit
  sudo apt-get update
  sudo apt-get install -y nvidia-container-toolkit

  # 5. Configure the Docker runtime
  sudo nvidia-ctk runtime configure --runtime=docker

  # 6. Restart Docker
  sudo systemctl restart docker

  # 7. Verify
  docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi

STEPS
}

# ─── read-only checks ────────────────────────────────────────────────────────

_check_nvidia_smi() {
    _info "Checking host nvidia-smi..."
    if command -v nvidia-smi >/dev/null 2>&1; then
        if nvidia-smi -L 2>/dev/null | grep -q 'GPU '; then
            _pass "nvidia-smi is working. Detected GPUs:"
            nvidia-smi -L 2>/dev/null | sed 's/^/        /'
        else
            _fail "nvidia-smi found but no GPUs listed — check your NVIDIA driver installation."
        fi
    else
        _fail "nvidia-smi not found — install the NVIDIA driver for your distribution."
        _info "No NVIDIA GPU? Skip this script — the NVIDIA overlay is not needed for CPU-only use."
    fi
    echo
}

# WSL2 snap Docker cannot see /usr/lib/wsl/lib/libdxcore.so from its confined
# namespace, so NVIDIA passthrough fails until the user switches to non-snap
# Docker. DockerRootDir identifies snap installs more reliably than snap(8).
_is_wsl() {
    grep -qi microsoft /proc/version 2>/dev/null && return 0
    [ -d /usr/lib/wsl ] && return 0
    return 1
}

_is_docker_snap() {
    case "$(docker info --format '{{.DockerRootDir}}' 2>/dev/null)" in
        */snap/docker/*|*/snap.docker/*) return 0 ;;
    esac
    return 1
}

# Returns 1 if Docker is unavailable (callers should stop further GPU checks).
_check_docker() {
    _info "Checking Docker..."
    if ! command -v docker >/dev/null 2>&1; then
        _fail "docker not found — install Docker: https://docs.docker.com/engine/install/"
        echo "Cannot continue without Docker."
        return 1
    fi
    if docker info >/dev/null 2>&1; then
        _pass "Docker daemon is running."
    else
        _fail "Docker daemon is not running or current user lacks permission."
        _info "Try: sudo systemctl start docker"
        _info "Or add your user to the docker group: sudo usermod -aG docker \$USER"
        echo "Cannot continue — GPU passthrough test requires a running Docker daemon."
        return 1
    fi
    echo
}

_check_gpu_passthrough() {
    _info "Testing GPU passthrough (may pull image on first run):"
    _info "  docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi"
    echo
    if docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi 2>&1; then
        echo
        _GPU_PASSTHROUGH_OK=1
        _pass "GPU passthrough is working — the NVIDIA compose overlay should work."
        _info "Passthrough means Docker can see your GPU. It does NOT guarantee"
        _info "llama.cpp will use CUDA. If Cookbook logs show:"
        _info "  'Unable to find cudart library'"
        _info "  'Could NOT find CUDAToolkit' / 'CUDA Toolkit not found'"
        _info "  tensors or layers assigned to CPU"
        _info "that is a Cookbook/llama.cpp CUDA build or runtime issue, not a"
        _info "passthrough failure. Re-install the serve engine via"
        _info "Cookbook -> Dependencies to get a CUDA-enabled build."
        if [ "${OPT_ENABLE_OVERLAY}" -eq 0 ]; then
            _info "Enable the overlay in .env with:"
            _info "  scripts/check-docker-gpu.sh --enable-nvidia-overlay"
        fi
    else
        echo
        _fail "GPU passthrough failed. Check these steps in order:"
        echo
        if _is_wsl && _is_docker_snap; then
            _warn "Detected: Docker installed via snap, running on WSL2."
            _warn "This is a known incompatibility, not a toolkit/config problem:"
            _warn "  snap confines Docker's mount namespace, so it cannot see the"
            _warn "  WSL2-injected GPU library at /usr/lib/wsl/lib/libdxcore.so even"
            _warn "  though the file exists on the host. Installing/reconfiguring"
            _warn "  nvidia-container-toolkit will NOT fix this — the numbered"
            _warn "  steps below will not help until Docker itself is replaced."
            echo
            _info "Fix: remove snap Docker and install the official apt-based Docker"
            _info "Engine instead (unsandboxed, can see /usr/lib/wsl/lib):"
            echo
            echo "  sudo snap remove docker"
            echo "  # then follow: https://docs.docker.com/engine/install/ubuntu/"
            echo "  sudo nvidia-ctk runtime configure --runtime=docker"
            echo "  sudo systemctl restart docker"
            echo
            _info "Re-run this script afterward to confirm passthrough works."
            echo
        fi
        echo "  1. Install NVIDIA Container Toolkit (if not already installed):"
        echo "     Arch:    sudo pacman -S nvidia-container-toolkit"
        echo "     Debian:  sudo apt install nvidia-container-toolkit"
        echo "     Fedora:  sudo dnf install nvidia-container-toolkit"
        echo "     Full guide: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html"
        echo
        echo "  2. Configure the Docker runtime:"
        echo "     sudo nvidia-ctk runtime configure --runtime=docker"
        echo
        echo "  3. Restart Docker:"
        echo "     sudo systemctl restart docker"
        echo
        echo "  Then re-run this script to confirm."
        echo
        _warn "Without GPU passthrough, Cookbook will detect the iGPU, another card, or"
        _warn "CPU instead of your NVIDIA GPU — model recommendations will use the wrong VRAM."
        _info "Run with --print-install-commands to see OS-specific commands."
        _info "Run with --install-nvidia-toolkit to install on Ubuntu/Debian."
    fi
    echo
}

# ─── --enable-nvidia-overlay ─────────────────────────────────────────────────

_enable_nvidia_overlay() {
    echo "=== Enabling NVIDIA compose overlay ==="
    echo

    local _env_file="${REPO_ROOT}/.env"
    local _env_example="${REPO_ROOT}/.env.example"
    local _overlay_fragment="docker/gpu.nvidia.yml"
    local _backup_ts
    _backup_ts="$(date +%Y%m%d-%H%M%S)"

    # Ensure .env exists
    if [ ! -f "${_env_file}" ]; then
        if [ -f "${_env_example}" ]; then
            _info ".env not found. .env.example is available."
            local _do_copy=0
            if [ "${OPT_YES}" -eq 1 ]; then
                _do_copy=1
            elif _confirm "Copy .env.example to .env?"; then
                _do_copy=1
            fi
            if [ "${_do_copy}" -eq 1 ]; then
                if ! cp "${_env_example}" "${_env_file}"; then
                    _fail "Failed to copy .env.example to .env."
                    return 1
                fi
                _pass "Copied .env.example to .env."
            else
                _fail ".env is required to set COMPOSE_FILE — aborted."
                return 1
            fi
        else
            _fail ".env not found and .env.example is missing."
            _info "Create a .env file in the repo root, then re-run."
            return 1
        fi
    fi

    # Read current active (uncommented) COMPOSE_FILE value, if any
    local _current_cf
    _current_cf="$(grep '^COMPOSE_FILE=' "${_env_file}" | tail -1 | cut -d= -f2-)"

    # Idempotency check
    if echo "${_current_cf}" | grep -qF "${_overlay_fragment}"; then
        _pass "COMPOSE_FILE already includes the NVIDIA overlay — nothing to change."
        echo
        _info "Start or restart Odysseus to apply:"
        _info "  docker compose up -d --build"
        return 0
    fi

    # Back up .env before any edit
    local _backup="${_env_file}.bak.${_backup_ts}"
    if ! cp "${_env_file}" "${_backup}"; then
        _fail "Failed to create backup of .env — aborting to avoid data loss."
        return 1
    fi
    _info "Backup created: .env.bak.${_backup_ts}"

    local _new_cf=""
    if [ -z "${_current_cf}" ]; then
        # No active COMPOSE_FILE line — append one
        _new_cf="docker-compose.yml:${_overlay_fragment}"
        if ! printf '\nCOMPOSE_FILE=%s\n' "${_new_cf}" >> "${_env_file}"; then
            _fail "Failed to write COMPOSE_FILE to .env."
            return 1
        fi
    else
        # Existing COMPOSE_FILE — append the overlay to the existing value
        _new_cf="${_current_cf}:${_overlay_fragment}"
        local _tmp="${_env_file}.tmp"
        if ! sed "s|^COMPOSE_FILE=.*|COMPOSE_FILE=${_new_cf}|" "${_env_file}" > "${_tmp}"; then
            _fail "Failed to update COMPOSE_FILE in .env."
            rm -f "${_tmp}"
            return 1
        fi
        if ! mv "${_tmp}" "${_env_file}"; then
            _fail "Failed to write updated .env."
            rm -f "${_tmp}"
            return 1
        fi
    fi

    _pass "COMPOSE_FILE set to: ${_new_cf}"
    echo
    _info "Start or restart Odysseus with the NVIDIA overlay:"
    _info "  docker compose up -d --build"
    echo
    _info "To undo, restore the backup:"
    _info "  cp ${_backup} ${_env_file}"
}

# ─── mode: default read-only diagnostic ──────────────────────────────────────

_mode_check() {
    echo "=== Odysseus Docker GPU diagnostic ==="
    echo
    _check_nvidia_smi
    _check_docker || { echo "=== Results: ${PASS} passed, ${FAIL} failed ==="; return 1; }
    _check_gpu_passthrough

    if [ "${OPT_ENABLE_OVERLAY}" -eq 1 ]; then
        if [ "${_GPU_PASSTHROUGH_OK}" -eq 0 ]; then
            # Hard gate: broken passthrough blocks .env edits regardless of --yes.
            # Writing COMPOSE_FILE before passthrough works causes Odysseus to fail
            # at startup, so this is not a prompt — it is a stop.
            _fail "GPU passthrough is not working — .env will not be modified."
            _info "Fix passthrough first, then re-run with --enable-nvidia-overlay:"
            _info "  Ubuntu/Debian: scripts/check-docker-gpu.sh --install-nvidia-toolkit"
            _info "  Other distros: scripts/check-docker-gpu.sh --print-install-commands"
            echo
        else
            _enable_nvidia_overlay
        fi
    fi

    echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
    [ "${FAIL}" -eq 0 ]
}

# ─── mode: --print-install-commands ──────────────────────────────────────────

_mode_print() {
    echo "=== NVIDIA Container Toolkit — install commands ==="
    echo
    _info "Detected system: $(_distro_label)"
    echo

    if _is_debian_family; then
        _info "Ubuntu/Debian — recommended install commands:"
        _debian_install_steps
        _info "After running these, re-run the diagnostic to confirm:"
        _info "  scripts/check-docker-gpu.sh"
    else
        case "${DISTRO_ID}" in
            fedora|rhel|centos|rocky|almalinux)
                _info "Fedora/RHEL — install commands:"
                echo
                echo "  sudo dnf install -y nvidia-container-toolkit"
                echo "  sudo nvidia-ctk runtime configure --runtime=docker"
                echo "  sudo systemctl restart docker"
                echo "  docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi"
                ;;
            opensuse*|sles)
                _info "OpenSUSE/SLES — install commands:"
                echo
                echo "  sudo zypper install nvidia-container-toolkit"
                echo "  sudo nvidia-ctk runtime configure --runtime=docker"
                echo "  sudo systemctl restart docker"
                echo "  docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi"
                ;;
            arch|manjaro|endeavouros)
                _info "Arch Linux — install commands:"
                echo
                echo "  sudo pacman -S nvidia-container-toolkit"
                echo "  sudo nvidia-ctk runtime configure --runtime=docker"
                echo "  sudo systemctl restart docker"
                echo "  docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi"
                ;;
            *)
                _warn "Distro '${DISTRO_ID:-unknown}' is not specifically recognized."
                echo
                echo "  See the full guide for your distribution:"
                echo "  https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html"
                ;;
        esac
        echo
        _info "Automated install (--install-nvidia-toolkit) supports Ubuntu/Debian only."
        _info "For other distros, run the commands above manually, then re-run:"
        _info "  scripts/check-docker-gpu.sh"
    fi
}

# ─── mode: --install-nvidia-toolkit ──────────────────────────────────────────

_mode_install() {
    echo "=== NVIDIA Container Toolkit — interactive installer ==="
    echo

    if [ "$(uname -s)" != "Linux" ]; then
        _fail "Install mode is Linux-only. Detected: $(uname -s)"
        exit 1
    fi

    if ! _is_debian_family; then
        _fail "Automated install currently supports Ubuntu/Debian only."
        _info "Detected: $(_distro_label)"
        _info "Run --print-install-commands to see manual steps for your distro."
        exit 1
    fi

    _info "Detected system: $(_distro_label)"
    echo

    echo "This will run the following commands with sudo:"
    _debian_install_steps

    if [ "${OPT_YES}" -eq 0 ]; then
        if ! _confirm "Proceed with the above steps?"; then
            echo "Aborted — nothing was changed."
            exit 0
        fi
        echo
    fi

    # Step 1: prerequisites
    _step "Updating package lists..."
    sudo apt-get update -qq || { _fail "apt-get update failed."; exit 1; }
    _step "Installing prerequisites (curl, gpg)..."
    sudo apt-get install -y curl gpg || { _fail "Failed to install prerequisites."; exit 1; }
    _pass "Prerequisites ready."
    echo

    # Step 2: signing key
    _step "Adding NVIDIA GPG signing key..."
    curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
        | sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
        || { _fail "Failed to add NVIDIA GPG key."; exit 1; }
    _pass "Signing key added."
    echo

    # Step 3: apt repository
    _step "Adding NVIDIA apt repository..."
    curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
        | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
        | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list > /dev/null \
        || { _fail "Failed to add NVIDIA apt repository."; exit 1; }
    _pass "apt repository added."
    echo

    # Step 4: install toolkit
    _step "Installing nvidia-container-toolkit..."
    sudo apt-get update -qq || { _fail "apt-get update failed after adding NVIDIA repo."; exit 1; }
    sudo apt-get install -y nvidia-container-toolkit \
        || { _fail "Failed to install nvidia-container-toolkit."; exit 1; }
    _pass "nvidia-container-toolkit installed."
    echo

    # Step 5: configure Docker runtime
    _step "Configuring Docker runtime..."
    sudo nvidia-ctk runtime configure --runtime=docker \
        || { _fail "nvidia-ctk runtime configure failed."; exit 1; }
    _pass "Docker runtime configured."
    echo

    # Step 6: restart Docker
    _step "A Docker restart is required for the runtime change to take effect."
    local _do_restart=0
    if [ "${OPT_YES}" -eq 1 ]; then
        _do_restart=1
    elif _confirm "Restart Docker now?"; then
        _do_restart=1
    else
        _warn "Docker not restarted."
        _warn "Run 'sudo systemctl restart docker' before testing GPU passthrough."
    fi

    if [ "${_do_restart}" -eq 1 ]; then
        _step "Restarting Docker..."
        if sudo systemctl restart docker; then
            _pass "Docker restarted."
        else
            _fail "Docker restart failed — run: sudo systemctl restart docker"
        fi
    fi
    echo

    # Step 7: verification
    _info "Running GPU passthrough verification..."
    echo
    _check_docker || { echo "=== Results: ${PASS} passed, ${FAIL} failed ==="; exit 1; }
    _check_gpu_passthrough

    # Step 8: enable overlay (only if passthrough verified)
    if [ "${OPT_ENABLE_OVERLAY}" -eq 1 ]; then
        if [ "${_GPU_PASSTHROUGH_OK}" -eq 1 ]; then
            _enable_nvidia_overlay
        else
            _warn "GPU passthrough verification failed — skipping overlay setup."
            _warn "Fix the passthrough issue, then run:"
            _warn "  scripts/check-docker-gpu.sh --enable-nvidia-overlay"
            echo
        fi
    fi

    echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
    [ "${FAIL}" -eq 0 ]
}

# ─── dispatch ────────────────────────────────────────────────────────────────

case "${MODE}" in
    check)   _mode_check ;;
    print)   _mode_print ;;
    install) _mode_install ;;
esac
