#!/usr/bin/env bash

set -euo pipefail

PACKAGE_NAME="dreadnode"
MIN_PYTHON_MAJOR=3
MIN_PYTHON_MINOR=11
INSTALL_VERSION=""
DETECTED_OS=""
INSTALLER_USED=""
INSTALL_BIN_DIR=""
PATH_UPDATED=0
NEEDS_SHELL_RELOAD=0

COLOR_RED=""
COLOR_GREEN=""
COLOR_YELLOW=""
COLOR_BLUE=""
COLOR_BOLD=""
COLOR_RESET=""

has_command() {
    command -v "$1" >/dev/null 2>&1
}

init_colors() {
    if [ -t 1 ]; then
        COLOR_RED="\033[31m"
        COLOR_GREEN="\033[32m"
        COLOR_YELLOW="\033[33m"
        COLOR_BLUE="\033[34m"
        COLOR_BOLD="\033[1m"
        COLOR_RESET="\033[0m"
    fi
}

info() {
    printf "%b==>%b %s\n" "$COLOR_BLUE" "$COLOR_RESET" "$1"
}

warn() {
    printf "%bWarning:%b %s\n" "$COLOR_YELLOW" "$COLOR_RESET" "$1"
}

error() {
    printf "%bError:%b %s\n" "$COLOR_RED" "$COLOR_RESET" "$1" >&2
}

success() {
    printf "%b%s%b\n" "$COLOR_GREEN" "$1" "$COLOR_RESET"
}

banner() {
    printf "\n"
    printf "%b%s%b\n" "$COLOR_BOLD" "  Dreadnode CLI Installer" "$COLOR_RESET"
    printf "  https://docs.dreadnode.io\n"
    printf "\n"
}

usage() {
    cat <<'EOF'
Install Dreadnode CLI and SDK

Usage:
  install.sh [--version X.Y.Z] [--help]

Examples:
  curl -fsSL https://dreadnode.io/install.sh | bash
  curl -fsSL https://dreadnode.io/install.sh | bash -s -- --version 2.0.1
EOF
}

parse_args() {
    while [ $# -gt 0 ]; do
        case "$1" in
            --version)
                if [ $# -lt 2 ]; then
                    error "--version requires a value"
                    exit 1
                fi
                INSTALL_VERSION="$2"
                shift 2
                ;;
            --help|-h)
                usage
                exit 0
                ;;
            *)
                error "Unknown argument: $1"
                usage
                exit 1
                ;;
        esac
    done
}

detect_platform() {
    local arch=""

    case "$(uname -s)" in
        Darwin)
            DETECTED_OS="macOS"
            ;;
        Linux)
            DETECTED_OS="Linux"
            ;;
        MINGW*|MSYS*|CYGWIN*)
            error "Windows is not supported by this script. Use WSL or install from docs: https://docs.dreadnode.io"
            exit 1
            ;;
        *)
            error "Unsupported operating system: $(uname -s)"
            exit 1
            ;;
    esac

    case "$(uname -m)" in
        x86_64|amd64)
            arch="x64"
            ;;
        arm64|aarch64)
            arch="arm64"
            ;;
        *)
            error "Unsupported architecture: $(uname -m)"
            exit 1
            ;;
    esac

    # Detect Rosetta 2 on macOS: if running as x64 under translation on an
    # ARM Mac, report the native arm64 architecture instead.
    if [ "$DETECTED_OS" = "macOS" ] && [ "$arch" = "x64" ]; then
        if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then
            arch="arm64"
            info "Rosetta 2 detected, using native arm64"
        fi
    fi

    # Note musl libc on Linux (Alpine, Void, etc.) for diagnostics
    if [ "$DETECTED_OS" = "Linux" ]; then
        if [ -f /lib/libc.musl-x86_64.so.1 ] || [ -f /lib/libc.musl-aarch64.so.1 ] || (ldd /bin/ls 2>&1 | grep -q musl 2>/dev/null); then
            info "Detected musl libc (Alpine/Void Linux)"
        fi
    fi

    info "Platform: ${DETECTED_OS} (${arch})"
}

# Returns 0 if Python meets minimum version, 1 if too old, 2 if not found.
# Captured via variable assignment, not $? after `if`.
python_version_status() {
    if ! has_command python3; then
        echo "missing"
        return 0
    fi

    local py_major py_minor
    py_major="$(python3 -c 'import sys; print(sys.version_info[0])')"
    py_minor="$(python3 -c 'import sys; print(sys.version_info[1])')"

    if [ "$py_major" -gt "$MIN_PYTHON_MAJOR" ] || {
        [ "$py_major" -eq "$MIN_PYTHON_MAJOR" ] && [ "$py_minor" -ge "$MIN_PYTHON_MINOR" ]
    }; then
        echo "ok"
    else
        echo "old"
    fi
}

pick_installer() {
    if has_command uv; then
        printf "uv"
        return 0
    fi

    if has_command pipx; then
        printf "pipx"
        return 0
    fi

    # Skip pip/pip3 fallback -- modern distros (Debian 12+, Ubuntu 23+,
    # Fedora 38+, macOS Sonoma+) block `pip install --user` via PEP 668.
    # Auto-installing uv is more reliable than fighting externally-managed
    # Python environments.
    printf "none"
}

install_uv() {
    if has_command curl; then
        info "Installing uv package manager..."
        curl -LsSf https://astral.sh/uv/install.sh | INSTALLER_NO_MODIFY_PATH=1 sh || {
            error "Failed to install uv"
            exit 1
        }
    elif has_command wget; then
        info "Installing uv package manager..."
        wget -qO- https://astral.sh/uv/install.sh | INSTALLER_NO_MODIFY_PATH=1 sh || {
            error "Failed to install uv"
            exit 1
        }
    else
        error "Neither curl nor wget found, and uv is not installed."
        exit 1
    fi

    export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"

    if ! has_command uv; then
        error "uv installation completed but uv is not on PATH"
        warn "Try opening a new shell, then run: uv tool install ${PACKAGE_NAME}"
        exit 1
    fi
}

install_with_uv() {
    local spec="$PACKAGE_NAME"
    if [ -n "$INSTALL_VERSION" ]; then
        spec="${PACKAGE_NAME}==${INSTALL_VERSION}"
    fi

    info "Installing ${spec} with uv tool..."
    uv tool install --force --upgrade "$spec"
    INSTALLER_USED="uv"
}

install_with_pipx() {
    local py_status
    py_status="$(python_version_status)"
    if [ "$py_status" != "ok" ]; then
        error "pipx requires Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ on PATH"
        warn "Install Python first, or run this script again to use uv"
        exit 1
    fi

    local spec="$PACKAGE_NAME"
    if [ -n "$INSTALL_VERSION" ]; then
        spec="${PACKAGE_NAME}==${INSTALL_VERSION}"
    fi

    info "Installing ${spec} with pipx..."
    pipx install --force "$spec"
    INSTALLER_USED="pipx"
}

get_install_bin_dir() {
    local bin_dir=""

    case "$INSTALLER_USED" in
        uv)
            if has_command uv; then
                bin_dir="$(uv tool dir --bin 2>/dev/null || true)"
            fi
            ;;
        pipx)
            if has_command pipx; then
                bin_dir="$(pipx environment --value PIPX_BIN_DIR 2>/dev/null || true)"
            fi
            ;;
    esac

    if [ -z "$bin_dir" ]; then
        bin_dir="$HOME/.local/bin"
    fi

    printf "%s" "$bin_dir"
}

detect_shell_rc() {
    local shell_name=""

    shell_name="$(basename "${SHELL:-/bin/sh}")"

    case "$shell_name" in
        zsh)
            if [ "$DETECTED_OS" = "macOS" ]; then
                printf "%s" "$HOME/.zprofile"
            else
                printf "%s" "$HOME/.zshrc"
            fi
            ;;
        bash)
            if [ "$DETECTED_OS" = "macOS" ] && [ -f "$HOME/.bash_profile" ]; then
                printf "%s" "$HOME/.bash_profile"
            else
                printf "%s" "$HOME/.bashrc"
            fi
            ;;
        fish)
            printf "%s" "$HOME/.config/fish/config.fish"
            ;;
        *)
            printf "%s" "$HOME/.profile"
            ;;
    esac
}

detect_shell_rcs() {
    local shell_name=""

    shell_name="$(basename "${SHELL:-/bin/sh}")"

    case "$shell_name" in
        zsh)
            printf "%s\n%s" "$HOME/.zprofile" "$HOME/.zshrc"
            ;;
        bash)
            if [ "$DETECTED_OS" = "macOS" ] && [ -f "$HOME/.bash_profile" ]; then
                printf "%s\n%s" "$HOME/.bash_profile" "$HOME/.bashrc"
            else
                printf "%s\n%s" "$HOME/.profile" "$HOME/.bashrc"
            fi
            ;;
        *)
            detect_shell_rc
            ;;
    esac
}

update_shell_path() {
    local target_dir="$1"
    local shell_name=""
    local shell_rc=""
    local shell_rcs=""
    local path_line=""

    shell_name="$(basename "${SHELL:-/bin/sh}")"
    shell_rcs="$(detect_shell_rcs)"

    if [ "$shell_name" = "fish" ]; then
        path_line="fish_add_path $target_dir"
    else
        path_line="export PATH=\"$target_dir:\$PATH\""
    fi

    while IFS= read -r shell_rc; do
        [ -n "$shell_rc" ] || continue

        mkdir -p "$(dirname "$shell_rc")"

        if [ -f "$shell_rc" ] && grep -Fq "$target_dir" "$shell_rc"; then
            info "PATH is already configured in ${shell_rc}"
        else
            info "Adding ${target_dir} to ${shell_rc}"
            printf "\n%s\n" "$path_line" >>"$shell_rc"
            PATH_UPDATED=1
        fi
    done <<<"$shell_rcs"

    export PATH="$target_dir:$PATH"

    if ! has_command dreadnode; then
        NEEDS_SHELL_RELOAD=1
    fi
}

configure_path_if_needed() {
    local target_dir="$1"

    if verify_fresh_shell; then
        return 0
    fi

    case "$INSTALLER_USED" in
        uv)
            if has_command uv; then
                uv tool update-shell >/dev/null 2>&1 || true
            fi
            ;;
        pipx)
            if has_command pipx; then
                pipx ensurepath >/dev/null 2>&1 || true
            fi
            ;;
    esac

    update_shell_path "$target_dir"
}

verify_install() {
    local cli_cmd=""
    local install_bin_dir=""

    install_bin_dir="$(get_install_bin_dir)"
    INSTALL_BIN_DIR="$install_bin_dir"

    if has_command dreadnode; then
        cli_cmd="dreadnode"
    elif [ -x "$install_bin_dir/dreadnode" ]; then
        cli_cmd="$install_bin_dir/dreadnode"
    elif [ -x "$HOME/.local/bin/dreadnode" ]; then
        cli_cmd="$HOME/.local/bin/dreadnode"
        INSTALL_BIN_DIR="$HOME/.local/bin"
    fi

    if [ -z "$cli_cmd" ]; then
        error "Install completed, but 'dreadnode' was not found"
        print_path_help
        exit 1
    fi

    if ! "$cli_cmd" --help >/dev/null 2>&1; then
        error "'dreadnode' was found but failed to run"
        exit 1
    fi

    configure_path_if_needed "$INSTALL_BIN_DIR"

    success "Installed dreadnode CLI"
}

verify_fresh_shell() {
    local shell_bin="${SHELL:-/bin/sh}"

    if [ ! -x "$shell_bin" ]; then
        shell_bin="/bin/sh"
    fi

    env -i HOME="$HOME" SHELL="$shell_bin" "$shell_bin" -lc 'command -v dreadnode' >/dev/null 2>&1
}

# Detect the user's shell and print the exact commands to add ~/.local/bin
# to PATH permanently. Modelled after Factory CLI's installer.
print_path_help() {
    local target_dir="${INSTALL_BIN_DIR:-$HOME/.local/bin}"
    local shell_name=""
    local shell_rc=""

    shell_name="$(basename "${SHELL:-/bin/sh}")"
    shell_rc="$(detect_shell_rc)"

    warn "Add ${target_dir} to your PATH:"
    if [ "$shell_name" = "fish" ]; then
        printf "  fish_add_path %s\n" "$target_dir"
    else
        printf "  echo 'export PATH=\"%s:\$PATH\"' >> %s\n" "$target_dir" "$shell_rc"
    fi
    printf "  source %s\n" "$shell_rc"
    warn "Then run: dreadnode --help"
}

relaunch_login_shell_if_needed() {
    if [ "$NEEDS_SHELL_RELOAD" -eq 0 ]; then
        return 0
    fi

    if [ -n "${CI:-}" ]; then
        warn "Open a new terminal, then run: dreadnode"
        return 0
    fi

    if [ -t 0 ] && [ -t 1 ] && [ -r /dev/tty ] && [ -w /dev/tty ]; then
        info "Starting a new login shell so 'dreadnode' is available now..."
        printf "\n"
        exec "${SHELL:-/bin/sh}" -l </dev/tty >/dev/tty 2>&1
    fi

    warn "Open a new terminal, then run: dreadnode"
}

main() {
    init_colors
    parse_args "$@"
    banner
    detect_platform

    local py_status
    py_status="$(python_version_status)"

    case "$py_status" in
        ok)
            info "Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ detected"
            ;;
        old)
            warn "Python found but older than ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}; uv will manage its own Python"
            ;;
        missing)
            warn "python3 not found; uv will manage its own Python runtime"
            ;;
    esac

    local installer
    installer="$(pick_installer)"

    case "$installer" in
        uv)
            install_with_uv
            ;;
        pipx)
            install_with_pipx
            ;;
        none)
            install_uv
            install_with_uv
            ;;
    esac

    verify_install
    printf "\n"
    if verify_fresh_shell; then
        success "Installation complete!"
        printf "\n"

        if [ "$PATH_UPDATED" -eq 1 ]; then
            info "Updated your shell profile so '${INSTALL_BIN_DIR}' is on PATH"
        fi

        printf "  Next steps:\n"
        printf "    dreadnode            Build with Dreadnode\n"
        printf "    dreadnode --help     See available commands\n"
        printf "    dn --help            Shortcut alias\n"
        printf "\n"
        printf "    See https://docs.dreadnode.io for more\n"
        printf "\n"
    elif [ "$PATH_UPDATED" -eq 1 ]; then
        warn "Installed! Restart your terminal to use dreadnode and dn."
        printf "\n"
        warn "After restarting, run: dreadnode --help"
        printf "  If needed, run directly: %s/dreadnode --help\n" "${INSTALL_BIN_DIR:-$HOME/.local/bin}"
        printf "\n"
    else
        warn "dreadnode was installed, but PATH could not be verified in a new shell."
        print_path_help
        printf "\n"
    fi

    relaunch_login_shell_if_needed
}

if [ "${BASH_SOURCE[0]:-$0}" = "$0" ]; then
    main "$@"
fi
