#!/bin/bash
# noodl Installation Script
#
# Install latest version:
#   curl -fsSL https://install.noodlbox.io/install.sh | bash

set -euo pipefail

# Require bash (script uses arrays, [[ ]], etc.)
if [ -z "${BASH_VERSION:-}" ]; then
    echo "Error: This script requires bash. Run with: bash install.sh" >&2
    exit 1
fi

# Configuration
PROJECT_NAME="noodl"
BINARY_NAME="noodl"
INSTALL_BASE_DIR="$HOME/.noodlbox"
INSTALL_BIN_DIR="$HOME/.local/bin"
CDN_BASE_URL="https://install.noodlbox.io"
MANIFEST_URL="${CDN_BASE_URL}/releases/latest.json"
VERSION=""
FORCE_INSTALL=false
EXPECTED_CHECKSUM=""

# TTY-aware colors (no ANSI in CI/pipes/non-interactive shells)
if [ -t 2 ]; then
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    RED='\033[0;31m'
    NC='\033[0m'
else
    GREEN=''
    YELLOW=''
    RED=''
    NC=''
fi

# Cleanup function
cleanup() {
    if [ -n "${TEMP_DIR:-}" ] && [ -d "$TEMP_DIR" ]; then
        rm -rf "$TEMP_DIR"
    fi
}
trap cleanup EXIT

# Logging functions
log_info() {
    echo -e "  ${1}" >&2
}

log_success() {
    echo -e "${GREEN}✓${NC} ${1}" >&2
}

log_warning() {
    echo -e "${YELLOW}Warning:${NC} ${1}" >&2
}

log_error() {
    echo -e "${RED}Error:${NC} ${1}" >&2
}

error() {
    log_error "$1"
    exit 1
}

show_help() {
    cat << EOF
noodl Installation Script

Usage:
    curl -fsSL https://install.noodlbox.io/install.sh | bash
    $0 [OPTIONS]

Options:
    --force              Force installation even if binary already exists
    --help, -h           Show this help message

Examples:
    # Install latest version
    curl -fsSL https://install.noodlbox.io/install.sh | bash

    # Force reinstall latest
    curl -fsSL https://install.noodlbox.io/install.sh | bash -s -- --force

Environment Variables:
    NOODLBOX_SKIP_INIT      Skip first-time setup (auth + agent config) after installation

EOF
}

# Parse command line arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        --force)
            FORCE_INSTALL=true
            shift
            ;;
        --help|-h)
            show_help
            exit 0
            ;;
        *)
            error "Unknown option: $1"
            ;;
    esac
done

# Detect OS
detect_os() {
    local os
    case "$(uname -s)" in
        Linux*)  os="linux" ;;
        Darwin*) os="darwin" ;;
        *)       error "Unsupported operating system: $(uname -s)" ;;
    esac
    echo "$os"
}

# Detect architecture (with Rosetta 2 detection on macOS)
detect_arch() {
    local raw_arch
    raw_arch="$(uname -m)"

    # On macOS, uname -m reports x86_64 when running under Rosetta 2.
    # Detect the real hardware and install the native arm64 binary.
    if [ "$(uname -s)" = "Darwin" ] && [ "$raw_arch" = "x86_64" ]; then
        if sysctl -n sysctl.proc_translated 2>/dev/null | grep -q 1; then
            log_info "Detected Rosetta 2 translation — installing native arm64 binary"
            echo "arm64"
            return
        fi
    fi

    local arch
    case "$raw_arch" in
        x86_64|amd64)   arch="x64" ;;
        aarch64|arm64)   arch="arm64" ;;
        *)               error "Unsupported architecture: $raw_arch" ;;
    esac
    echo "$arch"
}

# Map OS-arch to platform identifier used in R2 paths
get_platform_id() {
    local os="$1"
    local arch="$2"

    case "${os}-${arch}" in
        darwin-arm64) echo "macos-arm64" ;;
        darwin-x64)   echo "macos-x64" ;;
        linux-x64)    echo "linux-x64" ;;
        linux-arm64)  echo "linux-arm64" ;;
        *)            error "Unsupported platform: ${os}-${arch}" ;;
    esac
}

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

ensure_dependencies() {
    if ! command_exists tar; then
        error "Required dependency 'tar' not found. Please install it and re-run the installer."
    fi
    if ! command_exists curl && ! command_exists wget; then
        error "Neither 'curl' nor 'wget' found. Please install one of them and re-run the installer."
    fi
}

# Compute SHA256 hash of a file
compute_sha256() {
    local file="$1"
    if command_exists sha256sum; then
        sha256sum "$file" | awk '{print $1}'
    elif command_exists shasum; then
        shasum -a 256 "$file" | awk '{print $1}'
    else
        return 1
    fi
}

# Download file with progress (TLS 1.2+, retries)
download_file() {
    local url="$1"
    local output="$2"

    if command_exists curl; then
        curl --proto '=https' --tlsv1.2 -fsSL --progress-bar \
            --retry 3 --retry-delay 1 "$url" -o "$output" || return 1
    elif command_exists wget; then
        wget --secure-protocol=TLSv1_2 -q --show-progress \
            --tries=3 --waitretry=1 "$url" -O "$output" || return 1
    else
        error "Neither curl nor wget found. Please install one of them."
    fi
}

# Download file silently (for JSON manifest)
download_silent() {
    local url="$1"
    local output="$2"

    if command_exists curl; then
        curl --proto '=https' --tlsv1.2 -fsSL \
            --retry 3 --retry-delay 1 "$url" -o "$output" || return 1
    elif command_exists wget; then
        wget --secure-protocol=TLSv1_2 -q \
            --tries=3 --waitretry=1 "$url" -O "$output" || return 1
    else
        error "Neither curl nor wget found."
    fi
}

# Parse JSON manifest using python3 (robust) with awk fallback (best-effort)
parse_manifest() {
    local manifest_file="$1"
    local platform_id="$2"

    if command_exists python3; then
        eval "$(python3 -c "
import json, sys
with open('${manifest_file}') as f:
    m = json.load(f)
v = m.get('version', '')
print(f'PARSED_VERSION={v}')
for p in m.get('platforms', []):
    if p.get('platform') == '${platform_id}':
        print(f'PARSED_CHECKSUM={p.get(\"checksum_sha256\", \"\")}')
        sys.exit(0)
print('PARSED_CHECKSUM=')
" 2>/dev/null)" || {
            # python3 failed, fall through to awk
            parse_manifest_awk "$manifest_file" "$platform_id"
            return
        }
    else
        parse_manifest_awk "$manifest_file" "$platform_id"
        return
    fi

    VERSION="${PARSED_VERSION:-}"
    EXPECTED_CHECKSUM="${PARSED_CHECKSUM:-}"
}

# Fallback: line-oriented awk parser (works if JSON is pretty-printed)
parse_manifest_awk() {
    local manifest_file="$1"
    local platform_id="$2"

    VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$manifest_file" | head -1 | sed 's/.*"\([^"]*\)"/\1/')

    # Match exact platform string (anchored with quotes to avoid substring matches)
    EXPECTED_CHECKSUM=$(awk -v platform="\"${platform_id}\"" '
        /"platform"/ && index($0, platform) { found=1 }
        found && /"checksum_sha256"/ {
            gsub(/.*"checksum_sha256"[[:space:]]*:[[:space:]]*"/, "")
            gsub(/".*/, "")
            print
            exit
        }
    ' "$manifest_file")
}

# Resolve version, download URL, and checksum from CDN manifest
resolve_from_manifest() {
    local os="$1"
    local arch="$2"
    local platform_id
    platform_id=$(get_platform_id "$os" "$arch")

    local manifest_file="${TEMP_DIR}/latest.json"

    log_info "Fetching version manifest..."
    if ! download_silent "$MANIFEST_URL" "$manifest_file"; then
        error "Failed to fetch version manifest from $MANIFEST_URL"
    fi

    # Parse manifest (sets VERSION and EXPECTED_CHECKSUM)
    parse_manifest "$manifest_file" "$platform_id"

    if [ -z "$VERSION" ]; then
        error "Could not determine latest version from manifest"
    fi
    # Ensure v prefix
    case "$VERSION" in
        v*) : ;;
        *) VERSION="v$VERSION" ;;
    esac

    if [ -z "$EXPECTED_CHECKSUM" ]; then
        error "No checksum found in manifest for platform $platform_id"
    fi

    # Download URL points to R2 CDN (latest binaries overwritten each release)
    DOWNLOAD_URL="${CDN_BASE_URL}/releases/latest/${platform_id}/noodl.tar.gz"

    log_info "Version: $VERSION"
    log_info "Platform: ${platform_id}"
}

# Verify SHA256 checksum (mandatory — errors on failure)
verify_checksum() {
    local file="$1"

    if [ -z "$EXPECTED_CHECKSUM" ]; then
        error "No checksum available. Cannot verify integrity."
    fi

    log_info "Verifying checksum..."
    local actual_checksum
    actual_checksum=$(compute_sha256 "$file") || error "No SHA256 tool found. Cannot verify integrity."

    if [ "$EXPECTED_CHECKSUM" != "$actual_checksum" ]; then
        error "Checksum verification failed\nExpected: $EXPECTED_CHECKSUM\nActual:   $actual_checksum"
    fi

    log_success "Checksum verified"
}

check_root() {
    if [ "$(id -u)" = "0" ]; then
        log_warning "Running as root. Consider installing as a regular user."
    fi
}

prepare_install_dir() {
    INSTALL_APP_DIR="${INSTALL_BASE_DIR}/${BINARY_NAME}"

    if [ ! -d "$INSTALL_BASE_DIR" ]; then
        if ! mkdir -p "$INSTALL_BASE_DIR" 2>/dev/null; then
            error "Cannot create base installation directory: $INSTALL_BASE_DIR"
        fi
    fi

    if [ ! -d "$INSTALL_APP_DIR" ]; then
        if ! mkdir -p "$INSTALL_APP_DIR" 2>/dev/null; then
            error "Cannot create application directory: $INSTALL_APP_DIR"
        fi
    fi

    if [ ! -w "$INSTALL_APP_DIR" ]; then
        error "Cannot write to application directory: $INSTALL_APP_DIR"
    fi

    log_info "Install location: $INSTALL_APP_DIR"
}

# Validate we can replace existing installation (does NOT delete anything yet)
check_existing_installation() {
    local existing_symlink="${INSTALL_BIN_DIR}/${BINARY_NAME}"

    if [ -f "$existing_symlink" ] && [ ! -L "$existing_symlink" ]; then
        if [ "$FORCE_INSTALL" != true ] && ! "$existing_symlink" --version 2>/dev/null | grep -qi "noodl"; then
            error "A non-symlink file exists at $existing_symlink. Please remove it manually or use --force."
        fi
    fi
}

# Atomically replace old installation with verified new binary.
# Old installation is backed up and only removed after the new binary is confirmed working.
# Rolls back on any failure.
atomic_replace() {
    local existing_symlink="${INSTALL_BIN_DIR}/${BINARY_NAME}"
    local backup_dir=""

    # Back up existing installation (if any)
    if [ -d "$INSTALL_APP_DIR" ] && [ -n "$(ls -A "$INSTALL_APP_DIR" 2>/dev/null)" ]; then
        backup_dir="${TEMP_DIR}/backup-$(date +%s)"
        log_info "Backing up existing installation..."
        cp -a "$INSTALL_APP_DIR" "$backup_dir"
    fi

    # Attempt install — remove old, move new in
    rm -rf "$INSTALL_APP_DIR"
    mkdir -p "$INSTALL_APP_DIR/bin"

    if ! cp "${TEMP_DIR}/verified-binary" "$INSTALL_APP_DIR/bin/$BINARY_NAME"; then
        # Rollback: restore backup
        if [ -n "$backup_dir" ] && [ -d "$backup_dir" ]; then
            log_warning "Install failed — rolling back to previous version"
            rm -rf "$INSTALL_APP_DIR"
            mv "$backup_dir" "$INSTALL_APP_DIR"
        fi
        error "Failed to install new binary"
    fi
    chmod +x "$INSTALL_APP_DIR/bin/$BINARY_NAME"

    # Replace symlink
    if [ -L "$existing_symlink" ] || [ -f "$existing_symlink" ]; then
        rm -f "$existing_symlink"
    fi
    mkdir -p "$INSTALL_BIN_DIR"
    if ! ln -s "$INSTALL_APP_DIR/bin/$BINARY_NAME" "$existing_symlink"; then
        # Rollback: restore backup
        if [ -n "$backup_dir" ] && [ -d "$backup_dir" ]; then
            log_warning "Symlink failed — rolling back to previous version"
            rm -rf "$INSTALL_APP_DIR"
            mv "$backup_dir" "$INSTALL_APP_DIR"
            ln -sf "$backup_dir/bin/$BINARY_NAME" "$existing_symlink" 2>/dev/null || true
        fi
        error "Failed to create symlink"
    fi

    # Verify new binary runs
    if ! "$existing_symlink" --version >/dev/null 2>&1; then
        # Rollback: restore backup
        if [ -n "$backup_dir" ] && [ -d "$backup_dir" ]; then
            log_warning "New binary verification failed — rolling back to previous version"
            rm -f "$existing_symlink"
            rm -rf "$INSTALL_APP_DIR"
            mv "$backup_dir" "$INSTALL_APP_DIR"
            ln -sf "$backup_dir/bin/$BINARY_NAME" "$existing_symlink" 2>/dev/null || true
            error "New binary failed to execute. Rolled back to previous version."
        fi
        log_warning "Binary installed but may not work correctly"
    fi

    log_success "Installation complete"
}

# Download, verify checksum, extract, and stage the binary in TEMP_DIR.
# Does NOT touch the install directory — that happens in atomic_replace().
download_and_verify() {
    local url="$1"
    local temp_archive="${TEMP_DIR}/${PROJECT_NAME}-${VERSION}.tar.gz"

    echo
    echo "Downloading noodl ${VERSION}..."

    if ! download_file "$url" "$temp_archive"; then
        error "Failed to download from $url"
    fi

    if [ ! -s "$temp_archive" ]; then
        error "Downloaded file is empty"
    fi

    log_success "Downloaded"

    verify_checksum "$temp_archive"

    log_info "Extracting..."
    if ! tar -tf "$temp_archive" > /dev/null 2>&1; then
        error "Downloaded file is not a valid archive"
    fi

    local extract_dir="${TEMP_DIR}/extracted"
    mkdir -p "$extract_dir"

    if ! tar -xzf "$temp_archive" -C "$extract_dir"; then
        error "Failed to extract archive"
    fi

    # Find binary in tarball (supports multiple structures)
    # - noodl-{target}/noodl (cargo-dist structure)
    # - noodl/bin/noodl (old wrapped structure)
    # - bin/noodl (old direct structure)
    local source_dir="$extract_dir"
    local binary_source=""

    for dir in "$extract_dir"/noodl-*; do
        if [ -d "$dir" ] && [ -f "$dir/$BINARY_NAME" ]; then
            binary_source="$dir/$BINARY_NAME"
            break
        fi
    done

    if [ -z "$binary_source" ]; then
        if [ -d "$extract_dir/noodl" ]; then
            source_dir="$extract_dir/noodl"
        fi

        if [ ! -d "$source_dir/bin" ] || [ ! -f "$source_dir/bin/$BINARY_NAME" ]; then
            error "Invalid archive structure. Expected bin/$BINARY_NAME or noodl-{target}/$BINARY_NAME"
        fi

        binary_source="$source_dir/bin/$BINARY_NAME"
    fi

    # Stage verified binary for atomic_replace()
    cp "$binary_source" "${TEMP_DIR}/verified-binary"
    chmod +x "${TEMP_DIR}/verified-binary"

    log_success "Extracted and verified"
}

install_binary() {
    echo
    echo "Installing..."

    atomic_replace

    local symlink_path="${INSTALL_BIN_DIR}/${BINARY_NAME}"
    local version_info
    version_info=$("$symlink_path" --version 2>/dev/null | head -1)
    if [ -n "$version_info" ]; then
        log_success "$version_info"
    fi
}

update_path() {
    local current_shell
    current_shell="$(basename "${SHELL:-/bin/sh}")"
    local os_name
    os_name="$(uname -s)"
    local preferred_rc=""

    case "$current_shell" in
        zsh)  preferred_rc="${HOME}/.zshrc" ;;
        bash)
            if [ "$os_name" = "Darwin" ]; then
                preferred_rc="${HOME}/.bash_profile"
            else
                preferred_rc="${HOME}/.bashrc"
            fi
            ;;
        *)    preferred_rc="${HOME}/.profile" ;;
    esac

    local rc_files=(
        "${HOME}/.zshrc"
        "${HOME}/.bashrc"
        "${HOME}/.bash_profile"
        "${HOME}/.profile"
    )
    local updated_any=false
    local existing_any=false

    for shell_rc in "${rc_files[@]}"; do
        if [ -f "$shell_rc" ]; then
            existing_any=true
            break
        fi
    done

    for shell_rc in "${rc_files[@]}"; do
        if [ "$existing_any" = true ]; then
            [ -f "$shell_rc" ] || continue
        elif [ "$shell_rc" != "$preferred_rc" ]; then
            continue
        fi

        [ -f "$shell_rc" ] || touch "$shell_rc" 2>/dev/null || continue

        if grep -Fq "# Added by noodl installer" "$shell_rc" 2>/dev/null; then
            continue
        fi

        if grep -Eq '^[[:space:]]*(export[[:space:]]+)?PATH=.*((\$HOME|\${HOME}|~)/\.local/bin)' "$shell_rc" 2>/dev/null || \
           grep -Fq "$INSTALL_BIN_DIR" "$shell_rc" 2>/dev/null; then
            continue
        fi

        if [ ! -w "$shell_rc" ]; then
            continue
        fi

        {
            echo ""
            echo "# Added by noodl installer"
            echo "export PATH=\"$INSTALL_BIN_DIR:\$PATH\""
        } >> "$shell_rc"
        log_info "Added $INSTALL_BIN_DIR to PATH in $shell_rc"
        updated_any=true
    done

    if [ "$updated_any" = false ] && [ "$existing_any" = false ] && [ ! -f "$preferred_rc" ]; then
        touch "$preferred_rc" 2>/dev/null || return 0
        if [ -w "$preferred_rc" ]; then
            {
                echo ""
                echo "# Added by noodl installer"
                echo "export PATH=\"$INSTALL_BIN_DIR:\$PATH\""
            } >> "$preferred_rc"
            log_info "Added $INSTALL_BIN_DIR to PATH in $preferred_rc"
        fi
    fi

    # Fish shell support
    local fish_config="${HOME}/.config/fish/conf.d/noodl.fish"
    if [ -d "${HOME}/.config/fish" ] && [ ! -f "$fish_config" ]; then
        mkdir -p "${HOME}/.config/fish/conf.d" 2>/dev/null || true
        if [ -w "${HOME}/.config/fish/conf.d" ]; then
            printf '# Added by noodl installer\nfish_add_path %s\n' "$INSTALL_BIN_DIR" > "$fish_config"
            log_info "Added $INSTALL_BIN_DIR to PATH in $fish_config"
        fi
    fi
}

run_init() {
    if [ "${NOODLBOX_SKIP_INIT:-}" = "1" ]; then
        return 0
    fi

    echo
    echo "Running first-time setup..."

    if ! "$INSTALL_BIN_DIR/$BINARY_NAME" init -y 2>/dev/null; then
        log_warning "Initialization failed. Run 'noodl init' manually."
    fi
}

# Main installation process
main() {
    echo "noodl Installation"
    echo

    TEMP_DIR=$(mktemp -d)

    local platform
    platform=$(detect_os)
    local arch
    arch=$(detect_arch)

    log_info "System: ${platform}-${arch}"

    ensure_dependencies
    check_root

    resolve_from_manifest "$platform" "$arch"

    prepare_install_dir
    check_existing_installation
    download_and_verify "$DOWNLOAD_URL"
    install_binary
    update_path
    run_init
}

main "$@"
