#!/bin/sh
# islo CLI Installer
# https://islo.dev
#
# Usage: curl -fsSL https://islo.dev/install.sh | sh
#
# Options:
#   --version <tag>  Install specific version (e.g., v0.3.0)
#   --dir <path>     Installation directory
#   -h, --help       Show help
#
# Environment variables:
#   ISLO_VERSION     Specific version to install
#   ISLO_DIR         Installation directory
#   ISLO_RELEASES    Override releases URL (for testing)

set -e

RELEASES_BASE="${ISLO_RELEASES:-https://releases.islo.dev}"
BIN_NAME="islo"
INSTALL_VERSION="${ISLO_VERSION:-}"
INSTALL_DIR="${ISLO_DIR:-}"

# Terminal detection
IS_TTY=false
[ -t 1 ] && IS_TTY=true

# ANSI escape sequences
if $IS_TTY; then
    C_RESET='\033[0m'
    C_BOLD='\033[1m'
    C_DIM='\033[2m'
    C_RED='\033[91m'
    C_GREEN='\033[92m'
    C_YELLOW='\033[93m'
    C_BLUE='\033[94m'
else
    C_RESET='' C_BOLD='' C_DIM='' C_RED='' C_GREEN='' C_YELLOW='' C_BLUE=''
fi

# Progress indicator state
PROGRESS_PID=""
PROGRESS_MSG=""

show_progress() {
    PROGRESS_MSG="$1"
    $IS_TTY || { echo "$PROGRESS_MSG"; return; }
    
    (
        frames="▹▹▹▹▹ ▸▹▹▹▹ ▹▸▹▹▹ ▹▹▸▹▹ ▹▹▹▸▹ ▹▹▹▹▸"
        while true; do
            for frame in $frames; do
                printf "\r  ${C_BLUE}%s${C_RESET} %s" "$frame" "$PROGRESS_MSG"
                sleep 0.15
            done
        done
    ) &
    PROGRESS_PID=$!
}

hide_progress() {
    [ -z "${PROGRESS_PID:-}" ] && return
    kill "$PROGRESS_PID" 2>/dev/null || true
    wait "$PROGRESS_PID" 2>/dev/null || true
    PROGRESS_PID=""
    $IS_TTY && printf "\r\033[K"
}

log_ok() {
    hide_progress
    printf "  ${C_GREEN}▸${C_RESET} %b\n" "$1"
}

log_warn() {
    hide_progress
    printf "  ${C_YELLOW}▸${C_RESET} %b\n" "$1"
}

log_err() {
    hide_progress
    printf "  ${C_RED}▸${C_RESET} %b\n" "$1" >&2
}

die() {
    log_err "$1"
    exit 1
}

on_exit() {
    hide_progress
}
trap on_exit EXIT INT TERM

print_banner() {
    printf "\n"
    printf "  ${C_BOLD}islo CLI Installer${C_RESET}\n"
    printf "  ${C_DIM}Secure sandbox for AI-generated code${C_RESET}\n"
    printf "\n"
}

print_help() {
    cat <<'HELP'
islo CLI Installer

USAGE
    curl -fsSL https://islo.dev/install.sh | sh
    curl -fsSL https://islo.dev/install.sh | sh -s -- [OPTIONS]

OPTIONS
    --version <tag>    Install a specific version (e.g., v0.3.0)
    --dir <path>       Custom installation directory
    -h, --help         Print this help message

ENVIRONMENT
    ISLO_VERSION       Override version to install
    ISLO_DIR           Override installation directory
    ISLO_RELEASES      Override releases URL (testing)

EXAMPLES
    # Install latest
    curl -fsSL https://islo.dev/install.sh | sh

    # Install specific version
    curl -fsSL https://islo.dev/install.sh | sh -s -- --version v0.3.0

    # Custom install location
    ISLO_DIR=/opt/bin curl -fsSL https://islo.dev/install.sh | sh

HELP
}

# Parse command line arguments
while [ $# -gt 0 ]; do
    case "$1" in
        --version)
            [ -z "${2:-}" ] && die "--version requires a value"
            INSTALL_VERSION="$2"; shift 2 ;;
        --dir)
            [ -z "${2:-}" ] && die "--dir requires a value"
            INSTALL_DIR="$2"; shift 2 ;;
        -h|--help)
            print_help; exit 0 ;;
        -*)
            die "Unknown option: $1" ;;
        *)
            die "Unexpected argument: $1" ;;
    esac
done

# Fetch with HTTP status tracking
# Usage: fetch URL OUTFILE
# Sets: FETCH_STATUS (http code or "ERR")
fetch() {
    _url="$1"
    _out="$2"
    FETCH_STATUS="ERR"

    if command -v curl >/dev/null 2>&1; then
        FETCH_STATUS=$(curl -fsSL -w '%{http_code}' -o "$_out" "$_url" 2>/dev/null) || FETCH_STATUS="ERR"
    elif command -v wget >/dev/null 2>&1; then
        if wget -q -O "$_out" "$_url" 2>/dev/null; then
            FETCH_STATUS="200"
        fi
    else
        die "curl or wget is required"
    fi
}

# Determine OS and architecture
get_platform() {
    _os=$(uname -s | tr '[:upper:]' '[:lower:]')
    _arch=$(uname -m)

    case "$_arch" in
        x86_64|amd64)   _arch="x86_64" ;;
        aarch64|arm64)  _arch="aarch64" ;;
        *)              die "Unsupported architecture: $_arch" ;;
    esac

    case "$_os" in
        linux)  PLATFORM_TARGET="${_arch}-unknown-linux-musl" ;;
        darwin) PLATFORM_TARGET="${_arch}-apple-darwin" ;;
        *)      die "Unsupported OS: $_os" ;;
    esac
}

# Pick installation directory
resolve_install_dir() {
    if [ -n "$INSTALL_DIR" ]; then
        DEST="$INSTALL_DIR"
        return
    fi

    # Prefer standard locations if already in PATH
    for candidate in "$HOME/.local/bin" "$HOME/bin"; do
        case ":$PATH:" in
            *":$candidate:"*)
                DEST="$candidate"
                return
                ;;
        esac
    done

    # Default to a standard user bin directory and add it to PATH if needed
    DEST="$HOME/.local/bin"
}

path_contains_dir() {
    case ":$PATH:" in
        *":$1:"*) return 0 ;;
        *) return 1 ;;
    esac
}

shell_path_expr() {
    case "$1" in
        "$HOME")
            printf '$HOME'
            ;;
        "$HOME"/*)
            printf '$HOME/%s' "${1#$HOME/}"
            ;;
        *)
            printf '%s' "$1"
            ;;
    esac
}

append_unique_line() {
    _file="$1"
    _line="$2"
    _parent=$(dirname "$_file")

    mkdir -p "$_parent" 2>/dev/null || return 1
    [ -f "$_file" ] || : > "$_file" 2>/dev/null || return 1

    if awk -v needle="$_line" '$0 == needle { found = 1 } END { exit(found ? 0 : 1) }' "$_file"; then
        return 0
    fi

    if [ -s "$_file" ]; then
        printf '\n%s\n' "$_line" >> "$_file" 2>/dev/null || return 1
    else
        printf '%s\n' "$_line" >> "$_file" 2>/dev/null || return 1
    fi
}

ensure_path() {
    PATH_UPDATED=false
    PATH_UPDATED_FILES=""

    path_contains_dir "$DEST" && return

    _path_expr=$(shell_path_expr "$DEST")
    _export_line="export PATH=\"${_path_expr}:\$PATH\""
    _shell_name="${SHELL##*/}"
    [ -n "$_shell_name" ] || _shell_name="sh"

    case "$_shell_name" in
        bash)
            _targets="$HOME/.bashrc $HOME/.profile"
            ;;
        zsh)
            _targets="$HOME/.zshrc $HOME/.profile"
            ;;
        *)
            _targets="$HOME/.profile"
            ;;
    esac

    for _target in $_targets; do
        if append_unique_line "$_target" "$_export_line"; then
            PATH_UPDATED=true
            if [ -n "$PATH_UPDATED_FILES" ]; then
                PATH_UPDATED_FILES="${PATH_UPDATED_FILES}, ${_target}"
            else
                PATH_UPDATED_FILES="${_target}"
            fi
        fi
    done

    PATH="${DEST}:$PATH"
}

# Get latest version from server
resolve_version() {
    [ -n "$INSTALL_VERSION" ] && return

    show_progress "Checking for latest version"

    _tmp=$(mktemp)
    fetch "${RELEASES_BASE}/latest/version.txt" "$_tmp"

    case "$FETCH_STATUS" in
        200)
            INSTALL_VERSION=$(cat "$_tmp" | tr -d '[:space:]')
            rm -f "$_tmp"
            log_ok "Found version ${C_BOLD}${INSTALL_VERSION}${C_RESET}"
            ;;
        ERR)
            rm -f "$_tmp"
            die "Network error: unable to reach ${RELEASES_BASE}"
            ;;
        404)
            rm -f "$_tmp"
            die "No releases found at ${RELEASES_BASE}/latest/version.txt"
            ;;
        *)
            rm -f "$_tmp"
            die "Failed to check version (HTTP ${FETCH_STATUS})"
            ;;
    esac
}

# Verify SHA256 checksum
check_integrity() {
    _file="$1"
    _checksum_url="$2"

    show_progress "Verifying integrity"

    _tmp=$(mktemp)
    fetch "$_checksum_url" "$_tmp"

    if [ "$FETCH_STATUS" != "200" ]; then
        rm -f "$_tmp"
        log_warn "Checksum not available, skipping verification"
        return
    fi

    _expected=$(awk '{print $1}' "$_tmp")
    rm -f "$_tmp"

    if command -v sha256sum >/dev/null 2>&1; then
        _actual=$(sha256sum "$_file" | awk '{print $1}')
    elif command -v shasum >/dev/null 2>&1; then
        _actual=$(shasum -a 256 "$_file" | awk '{print $1}')
    else
        log_warn "No checksum tool available, skipping verification"
        return
    fi

    if [ "$_actual" != "$_expected" ]; then
        die "Integrity check failed (checksum mismatch)"
    fi

    log_ok "Integrity verified"
}

# Download and install
install_binary() {
    _ver_num="${INSTALL_VERSION#v}"
    _archive="${BIN_NAME}-${_ver_num}-${PLATFORM_TARGET}.tar.gz"
    _url="${RELEASES_BASE}/${INSTALL_VERSION}/${_archive}"
    _sha_url="${_url}.sha256"

    _workdir=$(mktemp -d)
    _archive_path="${_workdir}/${_archive}"

    show_progress "Downloading ${BIN_NAME} ${INSTALL_VERSION}"
    fetch "$_url" "$_archive_path"

    case "$FETCH_STATUS" in
        200)
            log_ok "Downloaded ${_archive}"
            ;;
        ERR)
            rm -rf "$_workdir"
            die "Network error during download"
            ;;
        404)
            rm -rf "$_workdir"
            die "Release not found: ${INSTALL_VERSION} for ${PLATFORM_TARGET}"
            ;;
        *)
            rm -rf "$_workdir"
            die "Download failed (HTTP ${FETCH_STATUS})"
            ;;
    esac

    check_integrity "$_archive_path" "$_sha_url"

    show_progress "Installing to ${DEST}"

    tar -xzf "$_archive_path" -C "$_workdir" 2>/dev/null || die "Failed to extract archive"

    mkdir -p "$DEST" 2>/dev/null || die "Cannot create directory: ${DEST}"

    if [ -w "$DEST" ]; then
        mv "$_workdir/$BIN_NAME" "$DEST/$BIN_NAME"
        chmod 755 "$DEST/$BIN_NAME"
    else
        die "Cannot write to ${DEST} (permission denied)"
    fi

    rm -rf "$_workdir"
    log_ok "Installed to ${C_BOLD}${DEST}/${BIN_NAME}${C_RESET}"
}

# Install shell completions
install_completions() {
    show_progress "Setting up shell completions"

    # Need the binary in PATH for this to work
    _islo_bin="$DEST/$BIN_NAME"
    
    if [ ! -x "$_islo_bin" ]; then
        log_warn "Could not find islo binary, skipping completions"
        return
    fi

    # Run completions install (auto-detects shell)
    if "$_islo_bin" completions install >/dev/null 2>&1; then
        log_ok "Shell completions installed"
    else
        # Not a critical failure - user can run manually
        log_warn "Could not auto-install completions (run 'islo completions install' later)"
    fi
}

print_next_steps() {
    printf "\n"

    if $PATH_UPDATED; then
        printf "  ${C_GREEN}PATH updated.${C_RESET} Added ${C_BOLD}%s${C_RESET} to %s\n" "$DEST" "$PATH_UPDATED_FILES"
        printf "  ${C_DIM}Open a new shell or run: export PATH=\"%s:\$PATH\"${C_RESET}\n" "$DEST"
        printf "\n"
    elif path_contains_dir "$DEST"; then
        printf "  ${C_GREEN}PATH ready.${C_RESET} ${C_BOLD}%s${C_RESET} is already on your PATH\n" "$DEST"
        printf "\n"
    fi

    printf "  ${C_GREEN}Ready!${C_RESET} Get started:\n"
    printf "\n"
    printf "  ${C_BOLD}1.${C_RESET} Authenticate\n"
    printf "     ${C_DIM}\$ islo login${C_RESET}\n"
    printf "\n"
    printf "  ${C_BOLD}2.${C_RESET} Connect integrations ${C_DIM}(once — picked up by all sandboxes)${C_RESET}\n"
    printf "     ${C_DIM}\$ islo login --tool github${C_RESET}   ${C_DIM}# private repos${C_RESET}\n"
    printf "     ${C_DIM}\$ islo login --tool claude${C_RESET}   ${C_DIM}# Claude Code agent${C_RESET}\n"
    printf "     ${C_DIM}\$ islo login --tool cursor${C_RESET}   ${C_DIM}# Cursor agent${C_RESET}\n"
    printf "\n"
    printf "  ${C_BOLD}3.${C_RESET} Start a sandbox from inside your project directory\n"
    printf "     ${C_DIM}\$ cd your-project${C_RESET}\n"
    printf "     ${C_DIM}\$ islo use${C_RESET}                     ${C_DIM}# interactive shell${C_RESET}\n"
    printf "     ${C_DIM}\$ islo use --agent claude${C_RESET}       ${C_DIM}# start Claude Code agent${C_RESET}\n"
    printf "     ${C_DIM}\$ islo use --agent cursor${C_RESET}       ${C_DIM}# start Cursor agent${C_RESET}\n"
    printf "\n"
    printf "  ${C_DIM}Docs: https://docs.islo.dev${C_RESET}\n"
    printf "\n"
}

main() {
    print_banner
    get_platform
    resolve_version
    resolve_install_dir
    install_binary
    ensure_path
    install_completions
    print_next_steps
}

main
