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

ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ANSIBLE_VENV="$ROOT_DIR/.venv-ansible"
ANSIBLE_PYTHON="$ANSIBLE_VENV/bin/python"
ANSIBLE_PLAYBOOK="$ANSIBLE_VENV/bin/ansible-playbook"
export ANSIBLE_CONFIG="$ROOT_DIR/ansible/ansible.cfg"

run_project_script() {
  local script_path=$1
  shift || true
  bash "$script_path" "$@"
}

usage() {
  cat <<'EOF'
Usage:
  ./labctl up
  ./labctl down
  ./labctl preflight
  ./labctl versions
  ./labctl assets lab2 [--refresh]
  ./labctl start [core|all|service...]
  ./labctl stop [all|service...]
  ./labctl status [all|service...]
  ./labctl urls
  ./labctl open kiln
  ./labctl logs <service>
EOF
}

transformerlab_version() {
  local version_file=$ROOT_DIR/ansible/group_vars/all.yml

  if [ ! -f "$version_file" ]; then
    printf '%s\n' "unknown"
    return
  fi

  sed -nE 's/^courseware_transformerlab_version:[[:space:]]*"([^"]+)".*/\1/p' "$version_file" | head -n 1
}

print_versions() {
  cat <<EOF
Pinned component versions:
  TransformerLab: $(transformerlab_version) (single-user pinned install)
  Ansible Core: 2.18.6
EOF
}

require_student_password_hash() {
  local current_host_profile

  current_host_profile=$(host_profile)
  if [ "$current_host_profile" = "macos" ]; then
    return
  fi

  if [ -n "${COURSEWARE_STUDENT_PASSWORD_HASH:-}" ]; then
    return
  fi

  cat <<'EOF' >&2
Missing COURSEWARE_STUDENT_PASSWORD_HASH.

Set a password hash for the managed `student` login before running this command. Example:

  export COURSEWARE_STUDENT_PASSWORD_HASH="$(openssl passwd -6 'student-password')"

Then rerun:
  ./labctl up
EOF
  exit 1
}

confirm_installation() {
  local response
  local tlab_version
  tlab_version=$(transformerlab_version)

  if [ ! -t 0 ]; then
    cat <<EOF >&2
WARNING: THIS SCRIPT WILL CONFIGURE YOUR ENVIRONMENT WILL THE FOLLOWING SOFTWARE:

- Ollama
- llama.cpp
- TransformerLab (single-user pinned to ${tlab_version})
- Open WebUI
- ChunkViz
- Embedding Atlas
- Promptfoo
- Unsloth Studio
- Kiln Desktop
- Course-specific support assets for lab 2 and lab 4

IT IS RECOMMENDED TO RUN THIS IN AN ISLOATED ENVIRONMENT (Dedicated WSL, VM, etc.)

This process may take a long time.

This command requires interactive confirmation. Re-run it from a terminal and answer the prompt.
EOF
    exit 1
  fi

  cat <<EOF
WARNING: THIS SCRIPT WILL CONFIGURE YOUR ENVIRONMENT WILL THE FOLLOWING SOFTWARE:

- Ollama
- llama.cpp
- TransformerLab (single-user pinned to ${tlab_version})
- Open WebUI
- ChunkViz
- Embedding Atlas
- Promptfoo
- Unsloth Studio
- Kiln Desktop
- Course-specific support assets for lab 2 and lab 4

IT IS RECOMMENDED TO RUN THIS IN AN ISLOATED ENVIRONMENT (Dedicated WSL, VM, etc.)

This process may take a long time.
EOF

  read -r -p "CONFIRM INSTALLATION (y/N): " response
  case "$response" in
    y|Y)
      ;;
    *)
      echo "Installation cancelled."
      exit 1
      ;;
  esac
}

host_is_wsl() {
  [ "$(uname -s)" = "Linux" ] && uname -r | grep -qiE 'microsoft|wsl'
}

host_is_macos() {
  [ "$(uname -s)" = "Darwin" ]
}

host_is_linux() {
  [ "$(uname -s)" = "Linux" ]
}

host_is_arm_mac() {
  host_is_macos && [ "$(uname -m)" = "arm64" ]
}

host_profile() {
  if host_is_wsl; then
    printf '%s\n' "wsl"
    return
  fi

  if host_is_macos; then
    printf '%s\n' "macos"
    return
  fi

  if host_is_linux && host_is_debian_family; then
    printf '%s\n' "native-debian-ubuntu"
    return
  fi

  printf '%s\n' "unsupported"
}

check_wsl_gpu_readiness() {
  if [ "$(host_profile)" != "wsl" ]; then
    return
  fi

  if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi >/dev/null 2>&1; then
    return
  fi

  cat <<'EOF' >&2
WSL GPU support is not ready yet, so the installer is stopping before Ansible runs.

This courseware expects WSL to already see your NVIDIA GPU.

Please do this on the Windows side first:
1. Install or update the current NVIDIA Windows driver with WSL/CUDA support.
2. Open Windows PowerShell and run: wsl --update
3. Fully restart WSL: wsl --shutdown
4. Reopen your Linux distro and confirm this works: nvidia-smi

If `nvidia-smi` still fails inside WSL:
- Reboot Windows
- Confirm WSL 2 is in use
- Confirm the GPU is NVIDIA and the Windows driver is recent

After `nvidia-smi` works inside WSL, rerun:
  bash deploy-courseware.sh
EOF
  exit 1
}

linux_cuda_toolkit_is_ready() {
  command -v nvcc >/dev/null 2>&1 && return 0
  [ -x /usr/local/cuda/bin/nvcc ] && return 0
  for candidate in /usr/local/cuda-*/bin/nvcc; do
    if [ -x "$candidate" ]; then
      return 0
    fi
  done
  [ -f /usr/local/cuda/include/cuda_runtime.h ] && return 0
  [ -f /usr/include/cuda_runtime.h ] && return 0
  return 1
}

linux_cuda_toolkit_package_is_available() {
  command -v apt-cache >/dev/null 2>&1 || return 1
  apt-cache show nvidia-cuda-toolkit >/dev/null 2>&1
}

print_linux_cuda_toolkit_guidance() {
  local package_hint
  package_hint="sudo apt update && sudo apt install -y nvidia-cuda-toolkit"

  if command -v apt-cache >/dev/null 2>&1; then
    if ! apt-cache show nvidia-cuda-toolkit >/dev/null 2>&1; then
      package_hint="Your distro does not expose nvidia-cuda-toolkit in its default apt sources, so add NVIDIA's CUDA repository for your Debian/Ubuntu release and install the toolkit from there."
    fi
  fi

  if host_is_wsl; then
    cat <<EOF >&2
CUDA Toolkit is still missing inside this WSL Linux environment, so the installer is stopping before Ansible runs.

WSL splits GPU support into two layers:
- Windows side: the NVIDIA driver makes the GPU visible to WSL
- Linux side: llama.cpp still needs the CUDA toolkit headers/compiler inside the distro

What to do:
1. Confirm the driver side works in WSL: nvidia-smi
2. Install the Linux-side CUDA toolkit
   $package_hint
3. Verify the toolkit:
   nvcc --version
   ls /usr/local/cuda/include/cuda_runtime.h

After that, rerun:
  bash deploy-courseware.sh
EOF
    return
  fi

  cat <<EOF >&2
CUDA Toolkit is not installed inside this Linux environment, so the installer is stopping before Ansible runs.

This courseware can only build CUDA-enabled llama.cpp when the Linux-side toolkit is present.

What to do:
1. Install the CUDA toolkit
   $package_hint
2. Verify the toolkit:
   nvcc --version
   ls /usr/local/cuda/include/cuda_runtime.h

After that, rerun:
  bash deploy-courseware.sh
EOF
}

check_linux_cuda_toolkit() {
  if [ "$(host_profile)" != "native-debian-ubuntu" ]; then
    return
  fi

  if linux_cuda_toolkit_is_ready; then
    return
  fi

  if linux_cuda_toolkit_package_is_available; then
    return
  fi

  print_linux_cuda_toolkit_guidance
  exit 1
}

resolve_python() {
  if [ -n "${PYTHON_BIN:-}" ] && command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
    command -v "${PYTHON_BIN}"
    return
  fi

  if command -v python3 >/dev/null 2>&1; then
    command -v python3
    return
  fi

  if command -v python >/dev/null 2>&1; then
    command -v python
    return
  fi

  cat <<'EOF' >&2
Python 3 was not found.

Install it first, then rerun this command:
- Debian/Ubuntu/WSL: sudo apt update && sudo apt install -y python3 python3-venv
- macOS: brew install python@3.11
EOF
  exit 1
}

host_is_debian_family() {
  [ -f /etc/os-release ] || return 1
  grep -qiE '^(ID|ID_LIKE)=(.*debian|debian.*)$' /etc/os-release
}

python_has_venv_support() {
  local python_bin=$1
  local probe_parent
  local probe_venv

  probe_parent=$(mktemp -d 2>/dev/null || mktemp -d -t labctl-venv-probe)
  probe_venv="$probe_parent/venv"

  if "$python_bin" -m venv "$probe_venv" >/dev/null 2>&1; then
    rm -rf "$probe_parent"
    return 0
  fi

  rm -rf "$probe_parent"
  return 1
}

install_debian_python_venv_support() {
  local python_bin=$1
  local installer=(apt-get)
  local versioned_venv_pkg
  local packages=(python3-venv python3-pip)

  if ! command -v apt-get >/dev/null 2>&1; then
    return 1
  fi

  if [ "$(id -u)" -ne 0 ]; then
    if ! command -v sudo >/dev/null 2>&1; then
      cat <<'EOF' >&2
Python can be found, but this Debian/Ubuntu system is missing the venv bootstrap packages that Ansible needs.

Install them, then rerun this command:
  apt-get update && apt-get install -y python3-venv python3-pip
EOF
      return 1
    fi

    if [ ! -t 0 ]; then
      cat <<'EOF' >&2
Python can be found, but this Debian/Ubuntu system still needs sudo access to install python3-venv and python3-pip.

Run this command from an interactive terminal so sudo can prompt for your password, or install the packages manually:
  sudo apt-get update && sudo apt-get install -y python3-venv python3-pip
EOF
      return 1
    fi

    echo "This step needs sudo to install missing Python packages. You may be prompted for your password."
    installer=(sudo apt-get)
  fi

  versioned_venv_pkg=$("$python_bin" -c "import sys; print(f'python{sys.version_info.major}.{sys.version_info.minor}-venv')")
  if command -v apt-cache >/dev/null 2>&1 && apt-cache show "$versioned_venv_pkg" >/dev/null 2>&1; then
    packages=("$versioned_venv_pkg" "${packages[@]}")
  fi

  echo "Installing missing Python venv support for this Debian/Ubuntu system..."
  "${installer[@]}" update
  "${installer[@]}" install -y "${packages[@]}"
}

ansible_venv_is_usable() {
  if [ ! -x "$ANSIBLE_PYTHON" ]; then
    return 1
  fi

  if [ ! -x "$ANSIBLE_PLAYBOOK" ]; then
    return 1
  fi

  "$ANSIBLE_PYTHON" -c "import ansible" >/dev/null 2>&1 || return 1
  "$ANSIBLE_PLAYBOOK" --version >/dev/null 2>&1
}

rebuild_ansible() {
  local python_bin

  python_bin=$(resolve_python)

  if ! python_has_venv_support "$python_bin"; then
    if host_is_debian_family; then
      install_debian_python_venv_support "$python_bin"
    fi
  fi

  if ! python_has_venv_support "$python_bin"; then
    cat <<'EOF' >&2
Python 3 is installed, but its virtual environment support is still unavailable.

Install the missing venv package for your platform, then rerun this command:
- Debian/Ubuntu/WSL: sudo apt update && sudo apt install -y python3-venv python3-pip
- macOS: brew reinstall python@3.11
EOF
    exit 1
  fi

  rm -rf "$ANSIBLE_VENV"
  echo "Preparing local installer runtime..."
  "$python_bin" -m venv "$ANSIBLE_VENV"
  "$ANSIBLE_PYTHON" -m pip install --upgrade pip
  "$ANSIBLE_PYTHON" -m pip install "ansible-core==2.18.6"
}

ensure_ansible() {
  if ansible_venv_is_usable; then
    return
  fi

  if [ -e "$ANSIBLE_VENV" ]; then
    echo "Refreshing installer runtime for this machine..."
  fi

  rebuild_ansible
}

sudo_keepalive_pid=""

ensure_sudo_session() {
  if [ "$(uname -s)" != "Linux" ]; then
    return
  fi

  if [ "$(id -u)" -eq 0 ]; then
    return
  fi

  if ! command -v sudo >/dev/null 2>&1; then
    cat <<'EOF' >&2
This installer needs sudo for Linux package setup, but `sudo` is not installed.

Install sudo or rerun this command as root, then try again.
EOF
    exit 1
  fi

  if sudo -n true >/dev/null 2>&1; then
    return
  fi

  if [ ! -t 0 ]; then
    cat <<'EOF' >&2
This installer needs sudo for Linux package setup.

Run this command from an interactive terminal so sudo can prompt for your password, then rerun the same `./labctl` command.
EOF
    exit 1
  fi

  echo "This step needs sudo for Linux package setup. You may be prompted for your password."
  sudo -v
}

start_sudo_keepalive() {
  if [ "$(uname -s)" != "Linux" ]; then
    return
  fi

  if [ "$(id -u)" -eq 0 ]; then
    return
  fi

  if ! command -v sudo >/dev/null 2>&1; then
    return
  fi

  (
    while true; do
      sudo -n true >/dev/null 2>&1 || exit 0
      sleep 60
    done
  ) &
  sudo_keepalive_pid=$!
}

stop_sudo_keepalive() {
  if [ -n "${sudo_keepalive_pid:-}" ]; then
    kill "$sudo_keepalive_pid" >/dev/null 2>&1 || true
    wait "$sudo_keepalive_pid" >/dev/null 2>&1 || true
    sudo_keepalive_pid=""
  fi
}

run_playbook() {
  local playbook=$1
  local escaped_root
  shift

  check_wsl_gpu_readiness
  check_linux_cuda_toolkit
  ensure_ansible
  ensure_sudo_session

  if [ ! -x "$ANSIBLE_PLAYBOOK" ]; then
    echo "Installer runtime is incomplete. Rebuilding it..."
    rebuild_ansible
  fi

  escaped_root=${ROOT_DIR//\\/\\\\}
  escaped_root=${escaped_root//\"/\\\"}

  start_sudo_keepalive
  trap stop_sudo_keepalive RETURN
  "$ANSIBLE_PLAYBOOK" \
    -e "{\"courseware_root\":\"$escaped_root\"}" \
    "$ROOT_DIR/ansible/playbooks/$playbook" \
    "$@"
}

require_arg() {
  if [ $# -lt 1 ]; then
    usage
    exit 1
  fi
}

handle_assets_command() {
  local asset_group=${1:-}
  shift || true

  case "$asset_group" in
    lab2)
      exec bash "$ROOT_DIR/scripts/bootstrap_lab2_assets.sh" "$@"
      ;;
    *)
      usage
      exit 1
      ;;
  esac
}

main() {
  local cmd=${1:-}
  shift || true

  case "$cmd" in
    up)
      confirm_installation
      require_student_password_hash
      run_playbook up.yml
      run_project_script "$ROOT_DIR/scripts/service_manager.sh" start all
      ;;
    down)
      run_project_script "$ROOT_DIR/scripts/service_manager.sh" stop all || true
      run_playbook down.yml
      ;;
    preflight)
      confirm_installation
      require_student_password_hash
      run_playbook up.yml --tags preflight
      ;;
    versions)
      print_versions
      ;;
    assets)
      require_arg "$@"
      handle_assets_command "$@"
      ;;
    start|stop|status|urls|open|logs)
      exec bash "$ROOT_DIR/scripts/service_manager.sh" "$cmd" "$@"
      ;;
    ""|-h|--help|help)
      usage
      ;;
    *)
      usage
      exit 1
      ;;
  esac
}

main "$@"
