#!/bin/bash
#
# Unblocked MCP Installer
#
# This script downloads and installs the Unblocked MCP server for macOS and Linux.
# It detects your platform, downloads the appropriate executable, and configures
# supported IDEs and Agents (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code).
#
# Usage:
#   curl -fsSL https://getunblocked.com/install.sh | bash
#   OR
#   ./install.sh [options]
#
# Options:
#   --api-url URL          Use a custom API URL for testing
#   --help                 Show this help message

set -e

# Parse command line arguments
API_URL_OVERRIDE=""
NO_AUTH=false
RESET=false

while [[ $# -gt 0 ]]; do
    case "$1" in
        --api-url)
            if [ $# -lt 2 ] || [[ "$2" == --* ]]; then
                echo "Error: --api-url requires a URL" >&2
                exit 1
            fi
            API_URL_OVERRIDE="$2"
            shift 2
            ;;
        --no-auth)
            NO_AUTH=true
            shift
            ;;
        --reset)
            RESET=true
            shift
            ;;
        --help|-h)
            cat << 'HELP_TEXT'
Unblocked MCP Installer

Usage:
  ./install.sh [options]

Options:
  --api-url URL    Use a custom API URL
                   Example: --api-url https://example.com/api

  --no-auth        Configure MCP clients without starting authentication
  --reset          Remove an existing installation

  --help, -h       Show this help message

Environment Variables:
  UNBLOCKED_API_URL         Override the API URL
  UNBLOCKED_BASE_URL        Use legacy direct download mode (skips API)

Examples:
  # Install from default API
  ./install.sh

  # Install with custom API URL
  ./install.sh --api-url https://example.com/api

  # Install using environment variable
  UNBLOCKED_API_URL=https://example.com/api ./install.sh

  # Pipe the installer without starting authentication
  curl -fsSL https://getunblocked.com/install-mcp.sh | bash -s -- --no-auth

HELP_TEXT
            exit 0
            ;;
        *)
            echo "Unknown option: $1" >&2
            echo "Use --help for usage information" >&2
            exit 1
            ;;
    esac
done

# Configuration
INSTALL_DIR="$HOME/.unblocked/bin"
VERSION="${UNBLOCKED_VERSION:-latest}"

# Determine API URL: command-line argument > environment variable > default
if [ -n "$API_URL_OVERRIDE" ]; then
    API_URL="$API_URL_OVERRIDE"
elif [ -n "${UNBLOCKED_API_URL:-}" ]; then
    API_URL="$UNBLOCKED_API_URL"
else
    API_URL="https://getunblocked.com/api"
fi

VERSION_ENDPOINT="$API_URL/versionInfo/public"

# Legacy fallback for direct URL downloads (for testing/development)
BASE_URL="${UNBLOCKED_BASE_URL:-}"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Tool availability flags
HAS_JQ=false
SHA256_CMD=""
MD5_CMD=""

# Temporary file tracking for cleanup
TEMP_FILE=""

# Cleanup function
cleanup() {
    if [ -n "$TEMP_FILE" ] && [ -f "$TEMP_FILE" ]; then
        rm -f "$TEMP_FILE"
    fi
}

# Set up cleanup on exit
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

# Logging functions
info() {
    echo -e "${GREEN}==>${NC} $1"
}

warn() {
    echo -e "${YELLOW}Warning:${NC} $1"
}

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

success() {
    echo -e "${GREEN}✓${NC} $1"
}

log() {
    echo "$1"
}

# Check if a directory is in PATH
dir_in_path() {
    local check_dir="$1"
    # Normalize the directory path if it exists
    if [[ -d "$check_dir" ]]; then
        check_dir=$(cd "$check_dir" 2>/dev/null && pwd) || return 1
    fi
    case ":$PATH:" in
        *":$check_dir:"*) return 0 ;;
        *) return 1 ;;
    esac
}

ensure_symlink() {
    local symlink_path="$1"
    local target_path="$2"

    if [[ -L "$symlink_path" ]]; then
        if [[ "$(readlink "$symlink_path")" == "$target_path" ]]; then
            log "Symlink already configured: $symlink_path"
            return 0
        fi
    elif [[ -e "$symlink_path" ]]; then
        warn "Not replacing existing file: $symlink_path"
        return 1
    fi

    if ln -sfn "$target_path" "$symlink_path" 2>/dev/null; then
        success "Created symlink: $symlink_path -> $target_path"
        return 0
    fi

    return 1
}

# Try to create a symlink in a directory that's already in PATH
try_symlink_in_path() {
    local binary_name="$1"

    # Preferred directories to symlink into (in order of preference)
    local preferred_dirs=(
        "$HOME/.local/bin"
        "$HOME/bin"
        "$HOME/.bin"
    )

    for dir in "${preferred_dirs[@]}"; do
        if dir_in_path "$dir"; then
            # Directory is in PATH, try to create symlink
            mkdir -p "$dir" 2>/dev/null || continue

            local symlink_path="$dir/$binary_name"
            local target_path="$INSTALL_DIR/$binary_name"

            if ensure_symlink "$symlink_path" "$target_path"; then
                return 0
            fi
        fi
    done

    return 1
}

# Update PATH in shell profile
update_shell_profile() {
    local binary_name="unblocked"

    # First, try to symlink into a directory already in PATH
    if try_symlink_in_path "$binary_name"; then
        # Symlink created, no need to modify shell profile
        return
    fi

    # Create ~/.local/bin and symlink unblocked there (instead of adding ~/.unblocked/bin to PATH)
    local local_bin_dir="$HOME/.local/bin"
    mkdir -p "$local_bin_dir" 2>/dev/null || true
    local symlink_path="$local_bin_dir/$binary_name"
    local target_path="$INSTALL_DIR/$binary_name"

    if ! ensure_symlink "$symlink_path" "$target_path"; then
        info "To use unblocked from any terminal, add it to your PATH:"
        echo "  export PATH=\"$INSTALL_DIR:\$PATH\""
        return
    fi

    # Fall back to modifying shell profile to add ~/.local/bin
    # Detect shell from $SHELL or default
    local default_shell="bash"
    if [[ "$(uname -s)" == "Darwin" ]]; then
        default_shell="zsh"
    fi
    local os_name
    os_name="$(uname -s)"

    local shell_name
    shell_name=$(basename "${SHELL:-$default_shell}")

    local shell_profile=""
    local path_export=""

    case "$shell_name" in
        zsh)
            shell_profile="$HOME/.zshrc"
            path_export="export PATH=\"\$HOME/.local/bin:\$PATH\""
            ;;
        bash)
            if [[ "$os_name" == "Darwin" ]]; then
                if [[ -f "$HOME/.bash_profile" ]]; then
                    shell_profile="$HOME/.bash_profile"
                elif [[ -f "$HOME/.bashrc" ]]; then
                    shell_profile="$HOME/.bashrc"
                else
                    shell_profile="$HOME/.bash_profile"
                fi
            else
                if [[ -f "$HOME/.bashrc" ]]; then
                    shell_profile="$HOME/.bashrc"
                elif [[ -f "$HOME/.bash_profile" ]]; then
                    shell_profile="$HOME/.bash_profile"
                else
                    shell_profile="$HOME/.bashrc"
                fi
            fi
            path_export="export PATH=\"\$HOME/.local/bin:\$PATH\""
            ;;
        fish)
            shell_profile="$HOME/.config/fish/config.fish"
            path_export="fish_add_path \"\$HOME/.local/bin\""
            ;;
        *)
            warn "Unknown shell: $shell_name"
            warn "Please add ~/.local/bin to your PATH manually:"
            echo "  export PATH=\"\$HOME/.local/bin:\$PATH\""
            return
            ;;
    esac

    if [[ -f "$shell_profile" ]] && grep -Fqx "$path_export" "$shell_profile" 2>/dev/null; then
        log "~/.local/bin already configured in $shell_profile"
        echo ""
        log "To use unblocked immediately, run:"
        echo "  export PATH=\"\$HOME/.local/bin:\$PATH\""
        return
    fi

    echo ""

    # Create config file if it doesn't exist
    if [[ ! -f "$shell_profile" ]]; then
        mkdir -p "$(dirname "$shell_profile")"
        touch "$shell_profile"
    fi

    # Add to PATH
    {
        echo ""
        echo "$path_export"
    } >> "$shell_profile"

    log "To use unblocked immediately, run:"
    echo "  $path_export"
}

# Reset function - removes installed binaries
reset_installation() {
    echo ""
    echo "=== Unblocked MCP Reset ==="
    echo ""

    local mcp_path="$INSTALL_DIR/unblocked"

    if [ -f "$mcp_path" ]; then
        info "Removing MCP binary: $mcp_path"
        rm -f "$mcp_path"
        info "MCP binary removed"
    else
        info "No MCP binary found at: $mcp_path"
    fi

    # Check if install directory is empty, remove if so
    if [ -d "$INSTALL_DIR" ] && [ -z "$(ls -A "$INSTALL_DIR")" ]; then
        info "Removing empty installation directory: $INSTALL_DIR"
        rmdir "$INSTALL_DIR"
    fi

    echo ""
    echo "=== Reset Complete ==="
    echo ""
    info "Note: IDE and Agent configurations were not removed"
    info "To fully reset, manually remove MCP entries from your IDE and Agent config files"
    echo ""
}

# Check for required prerequisites
check_prerequisites() {
    # Check for curl or wget
    if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
        error "Neither curl nor wget is available. Please install one of them."
        return 1
    fi

    # Check for jq (optional)
    if command -v jq >/dev/null 2>&1; then
        HAS_JQ=true
    fi

    # Check for SHA256 checksum tool (platform-specific)
    if command -v shasum >/dev/null 2>&1; then
        SHA256_CMD="shasum -a 256"
    elif command -v sha256sum >/dev/null 2>&1; then
        SHA256_CMD="sha256sum"
    else
        warn "Neither shasum nor sha256sum is available. SHA-256 verification is unavailable."
    fi

    # Check for MD5 checksum tool (platform-specific)
    if command -v md5 >/dev/null 2>&1; then
        MD5_CMD="md5 -q"
    elif command -v md5sum >/dev/null 2>&1; then
        MD5_CMD="md5sum"
    else
        warn "Neither md5 nor md5sum is available. Legacy MD5 verification is unavailable."
    fi

    # At least one checksum tool should be available
    if [ -z "$SHA256_CMD" ] && [ -z "$MD5_CMD" ]; then
        error "No checksum verification tools available. Cannot verify download integrity."
        return 1
    fi

    return 0
}

check_linux_glibc() {
    local os
    os="$(uname -s)"
    case "$os" in
        Darwin) return 0 ;;
        Linux) ;;
        *)
            error "The Unblocked MCP installer supports macOS and Linux only (detected: $os)."
            return 1
            ;;
    esac

    if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then
        return 0
    fi

    local libc_info=""
    if command -v ldd >/dev/null 2>&1; then
        libc_info="$(LC_ALL=C ldd --version 2>&1 || true)"
    fi
    case "$libc_info" in
        *GLIBC*|*glibc*|*"GNU libc"*|*"GNU C Library"*) return 0 ;;
    esac

    error "The Unblocked CLI requires glibc Linux. Alpine and other musl-based distributions are not supported."
    return 1
}

# Detect platform
detect_platform() {
    local os=$(uname -s)
    local arch=$(uname -m)

    case "$os" in
        Darwin)
            case "$arch" in
                arm64) echo "darwin-arm64" ;;
                x86_64) echo "darwin-x64" ;;
                *)
                    error "Unsupported macOS architecture: $arch"
                    return 1
                    ;;
            esac
            ;;
        Linux)
            case "$arch" in
                x86_64) echo "linux-x64" ;;
                aarch64|arm64) echo "linux-arm64" ;;
                *)
                    error "Unsupported Linux architecture: $arch"
                    return 1
                    ;;
            esac
            ;;
        *)
            error "Unsupported operating system: $os"
            return 1
            ;;
    esac
}

# Download a file with error handling
download_file() {
    local url="$1"
    local output="$2"
    local description="$3"

    info "Downloading $description..."

    if command -v curl >/dev/null 2>&1; then
        if ! curl -fsSL "$url" -o "$output"; then
            error "Failed to download $description from $url"
            return 1
        fi
    elif command -v wget >/dev/null 2>&1; then
        if ! wget -q -O "$output" "$url"; then
            error "Failed to download $description from $url"
            return 1
        fi
    else
        error "Neither curl nor wget is available. Please install one of them."
        return 1
    fi
}

# Fetch version info from API
fetch_version_info() {
    local temp_file=$(mktemp)

    if command -v curl >/dev/null 2>&1; then
        if ! curl -fsSL "$VERSION_ENDPOINT" -o "$temp_file"; then
            error "Failed to fetch version info from $VERSION_ENDPOINT"
            rm -f "$temp_file"
            return 1
        fi
    elif command -v wget >/dev/null 2>&1; then
        if ! wget -q -O "$temp_file" "$VERSION_ENDPOINT"; then
            error "Failed to fetch version info from $VERSION_ENDPOINT"
            rm -f "$temp_file"
            return 1
        fi
    else
        error "Neither curl nor wget is available."
        return 1
    fi

    cat "$temp_file"
    rm -f "$temp_file"
    return 0
}

# Parse JSON field using jq
parse_version_field_jq() {
    local json="$1"
    local field="$2"
    echo "$json" | jq -r ".versions[0].$field // empty"
}

# Parse JSON field using bash regex (fallback when jq not available)
parse_version_field_bash() {
    local json="$1"
    local field="$2"

    # Normalize JSON to single line and remove extra whitespace
    json=$(echo "$json" | tr -d '\n\r\t' | sed 's/ \+/ /g')

    # Extract field value using bash regex
    # Matches: "field": "value" or "field":"value"
    if [[ $json =~ \"$field\"[[:space:]]*:[[:space:]]*\"([^\"]*)\" ]]; then
        echo "${BASH_REMATCH[1]}"
        return 0
    fi

    return 1
}

# Parse JSON field (uses jq if available, otherwise bash regex)
parse_version_field() {
    local json="$1"
    local field="$2"

    if [ "$HAS_JQ" = true ]; then
        parse_version_field_jq "$json" "$field"
    else
        parse_version_field_bash "$json" "$field"
    fi
}

# Get MCP download URLs and checksums from version info
get_mcp_urls_from_version_info() {
    local platform="$1"
    local version_json="$2"
    local url_field=""
    local checksum_field=""

    # Map platform to API field names
    case "$platform" in
        darwin-arm64)
            url_field="cliDarwinARM64DownloadUrl"
            checksum_field="cliDarwinARM64DownloadChecksum"
            ;;
        darwin-x64)
            url_field="cliDarwinX64DownloadUrl"
            checksum_field="cliDarwinX64DownloadChecksum"
            ;;
        linux-x64)
            url_field="cliLinuxX64DownloadUrl"
            checksum_field="cliLinuxX64DownloadChecksum"
            ;;
        linux-arm64)
            url_field="cliLinuxARM64DownloadUrl"
            checksum_field="cliLinuxARM64DownloadChecksum"
            ;;
        *)
            error "Unsupported platform: $platform"
            return 1
            ;;
    esac

    # Extract URL and checksum
    DOWNLOAD_URL=$(parse_version_field "$version_json" "$url_field")
    DOWNLOAD_CHECKSUM=$(parse_version_field "$version_json" "$checksum_field")

    # Validate that we got a URL
    if [ -z "$DOWNLOAD_URL" ]; then
        error "MCP server not available for platform: $platform"
        error "This may indicate that MCP is not yet available for your platform, or the API response is invalid."
        return 1
    fi

    return 0
}

# Verify file checksum
verify_checksum() {
    local file="$1"
    local expected="$2"

    if [ -z "$expected" ]; then
        error "No checksum provided by API. Cannot verify download integrity."
        return 1
    fi

    # Detect checksum type based on length
    local checksum_length=${#expected}
    local checksum_cmd=""
    local checksum_type=""

    if [ "$checksum_length" -eq 32 ]; then
        # MD5 checksum (32 hex characters)
        if [[ ! "$expected" =~ ^[a-fA-F0-9]{32}$ ]]; then
            error "Invalid MD5 checksum format: $expected"
            return 1
        fi
        if [ -z "$MD5_CMD" ]; then
            error "MD5 checksum provided but md5/md5sum not available. Cannot verify integrity."
            return 1
        fi
        checksum_cmd="$MD5_CMD"
        checksum_type="MD5 (legacy)"
    elif [ "$checksum_length" -eq 64 ]; then
        # SHA256 checksum (64 hex characters)
        if [[ ! "$expected" =~ ^[a-fA-F0-9]{64}$ ]]; then
            error "Invalid SHA256 checksum format: $expected"
            return 1
        fi
        if [ -z "$SHA256_CMD" ]; then
            error "SHA256 checksum provided but shasum/sha256sum not available. Cannot verify integrity."
            return 1
        fi
        checksum_cmd="$SHA256_CMD"
        checksum_type="SHA-256"
    else
        error "Invalid checksum format: $expected (expected 32 chars for MD5 or 64 chars for SHA256)"
        return 1
    fi

    info "Verifying $checksum_type checksum..."
    local checksum_output
    if ! checksum_output=$($checksum_cmd "$file"); then
        error "$checksum_type verification failed to run"
        return 1
    fi
    local actual
    actual=$(echo "$checksum_output" | cut -d' ' -f1)

    # Compare checksums (case-insensitive, bash 3 compatible)
    local actual_lower=$(echo "$actual" | tr '[:upper:]' '[:lower:]')
    local expected_lower=$(echo "$expected" | tr '[:upper:]' '[:lower:]')

    if [ "$actual_lower" != "$expected_lower" ]; then
        error "Checksum verification failed!"
        error "Expected: $expected"
        error "Actual:   $actual"
        rm -f "$file"
        return 1
    fi

    info "Checksum verified successfully"
    return 0
}

has_controlling_terminal() {
    if (exec 3<>/dev/tty) 2>/dev/null; then
        return 0
    fi

    return 1
}

print_auth_instructions() {
    warn "$1"
    log "Run interactive OAuth later with:"
    echo "  unblocked auth"
    echo ""
    log "For headless use, provide a PAT or team token with UNBLOCKED_API_TOKEN or UNBLOCKED_API_TOKEN_FILE."
    log "To store a token in the owner-only credential file, pipe it to:"
    echo "  unblocked auth --with-token --credential-store agent-file"
    echo ""
}

# Main installation function
main() {
    echo ""
    echo "=== Unblocked MCP Installer ==="
    echo ""

    if ! check_linux_glibc; then
        exit 1
    fi

    # Check prerequisites
    if ! check_prerequisites; then
        exit 1
    fi

    # Detect platform
    local platform
    if ! platform=$(detect_platform); then
        exit 1
    fi
    info "Platform detected: $platform"

    # Show API URL being used (helpful for debugging)
    if [ "$API_URL" != "https://getunblocked.com/api" ]; then
        info "Using API: $API_URL"
    fi

    # Create install directory
    info "Creating installation directory: $INSTALL_DIR"
    mkdir -p "$INSTALL_DIR"

    local mcp_path="$INSTALL_DIR/unblocked"
    TEMP_FILE=$(mktemp "$INSTALL_DIR/.unblocked.XXXXXX")

    # Check for legacy fallback (direct URL override)
    if [ -n "$BASE_URL" ]; then
        info "Using legacy fallback mode (UNBLOCKED_BASE_URL is set)"
        local mcp_url="$BASE_URL/$VERSION/unblocked-$platform"

        if ! download_file "$mcp_url" "$TEMP_FILE" "MCP server"; then
            exit 1
        fi
        warn "Checksum verification skipped in legacy mode"
    else
        # Use API-based approach with checksum verification
        info "Fetching version information from API..."
        local version_json
        if ! version_json=$(fetch_version_info); then
            exit 1
        fi

        # Extract download URL and checksum for platform
        if ! get_mcp_urls_from_version_info "$platform" "$version_json"; then
            exit 1
        fi

        if ! download_file "$DOWNLOAD_URL" "$TEMP_FILE" "MCP server"; then
            exit 1
        fi

        # Verify checksum
        if ! verify_checksum "$TEMP_FILE" "$DOWNLOAD_CHECKSUM"; then
            exit 1
        fi

    fi

    chmod +x "$TEMP_FILE"
    mv -f "$TEMP_FILE" "$mcp_path"
    TEMP_FILE=""
    info "MCP server installed to: $mcp_path"

    # Update shell PATH
    update_shell_profile

    # Run installer mode to configure IDEs and Agents
    # The MCP server executable includes installer functionality via --install-mcp flag
    echo ""
    info "Configuring IDEs and Agents..."
    echo ""

    if "$mcp_path" --install-mcp; then
        echo ""
        info "MCP server installed to: $mcp_path"
        echo ""

        if [ "$NO_AUTH" = true ]; then
            print_auth_instructions "Authentication skipped because --no-auth was supplied."
        elif ! has_controlling_terminal; then
            print_auth_instructions "No controlling terminal is available; authentication was not started."
        else
            info "Starting authentication..."
            echo ""
            if ! "$mcp_path" --auth; then
                print_auth_instructions "Authentication failed."
            fi
        fi

        echo ""
        echo "=== Installation Complete ==="
        echo ""
        info "Reset your IDE(s) and Agents so they reload the Unblocked MCP configuration"
        info "Unblocked MCP will not be available to existing agent sessions until they are reset"
        echo ""
    else
        error "IDE and Agent configuration failed"
        exit 1
    fi
}

# Tests source installer functions without running the installer.
if [ "${UNBLOCKED_INSTALLER_TEST_SOURCE_ONLY:-}" = "1" ]; then
    return 0 2>/dev/null || exit 0
fi

# Check if running with sudo (not recommended)
if [ "$EUID" -eq 0 ]; then
    warn "Running as root is not recommended. This installer will install to your home directory."
    warn "Press Ctrl+C to cancel, or wait 5 seconds to continue..."
    sleep 5
fi

# Check for --reset flag
if [ "$RESET" = true ]; then
    reset_installation
    exit 0
fi

# Run main installation
main "$@"
