#!/bin/bash
# Claude Mnemonic - Remote Installation Script
# Usage: curl -sSL https://raw.githubusercontent.com/lukaszraczylo/claude-mnemonic/main/scripts/install.sh | bash
#
# Or with a specific version:
# curl -sSL https://raw.githubusercontent.com/lukaszraczylo/claude-mnemonic/main/scripts/install.sh | bash -s -- v1.0.0

set -e

INSTALLER_VERSION="1.1.0"

# Configuration
GITHUB_REPO="lukaszraczylo/claude-mnemonic"
INSTALL_DIR="$HOME/.claude/plugins/marketplaces/claude-mnemonic"
CACHE_DIR="$HOME/.claude/plugins/cache/claude-mnemonic/claude-mnemonic"
PLUGINS_FILE="$HOME/.claude/plugins/installed_plugins.json"
SETTINGS_FILE="$HOME/.claude/settings.json"
MARKETPLACES_FILE="$HOME/.claude/plugins/known_marketplaces.json"
PLUGIN_KEY="claude-mnemonic@claude-mnemonic"

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

info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

success() {
    echo -e "${GREEN}[OK]${NC} $1"
}

warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

error() {
    echo -e "${RED}[ERROR]${NC} $1"
    exit 1
}

# Gracefully stop worker processes (SIGTERM first, then SIGKILL after timeout)
graceful_stop_worker() {
    # Send SIGTERM first
    pkill -TERM -f 'claude-mnemonic.*worker' 2>/dev/null || true
    pkill -TERM -f '\.claude/plugins/.*/worker' 2>/dev/null || true
    if command -v lsof &> /dev/null; then
        lsof -ti :37777 2>/dev/null | xargs kill -TERM 2>/dev/null || true
    elif command -v ss &> /dev/null; then
        ss -tlnp 'sport = :37777' 2>/dev/null | awk 'NR>1 {print $6}' | grep -oP 'pid=\K[0-9]+' | xargs -r kill -TERM 2>/dev/null || true
    elif command -v fuser &> /dev/null; then
        fuser -k -TERM 37777/tcp 2>/dev/null || true
    fi

    # Wait up to 5 seconds for graceful shutdown
    local waited=0
    while [[ $waited -lt 5 ]]; do
        if ! pgrep -f 'claude-mnemonic.*worker' &>/dev/null && ! pgrep -f '\.claude/plugins/.*/worker' &>/dev/null; then
            return 0
        fi
        sleep 1
        waited=$((waited + 1))
    done

    # Force kill if still running
    pkill -9 -f 'claude-mnemonic.*worker' 2>/dev/null || true
    pkill -9 -f '\.claude/plugins/.*/worker' 2>/dev/null || true
    if command -v lsof &> /dev/null; then
        lsof -ti :37777 2>/dev/null | xargs kill -9 2>/dev/null || true
    elif command -v ss &> /dev/null; then
        ss -tlnp 'sport = :37777' 2>/dev/null | awk 'NR>1 {print $6}' | grep -oP 'pid=\K[0-9]+' | xargs -r kill -9 2>/dev/null || true
    elif command -v fuser &> /dev/null; then
        fuser -k 37777/tcp 2>/dev/null || true
    fi
    sleep 1

    # Remove stale PID cache to prevent hooks from using old worker info
    rm -f "$HOME/.claude-mnemonic/.worker-cache" 2>/dev/null || true

    # Verify process is gone
    if pgrep -f 'claude-mnemonic.*worker' &>/dev/null; then
        warn "Could not stop existing worker process"
    fi
}

# Detect OS and architecture
detect_platform() {
    local os arch

    case "$(uname -s)" in
        Darwin)
            os="darwin"
            ;;
        Linux)
            os="linux"
            ;;
        MINGW*|MSYS*|CYGWIN*)
            os="windows"
            ;;
        *)
            error "Unsupported operating system: $(uname -s)"
            ;;
    esac

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

    # Check for unsupported combinations
    if [[ "$os" == "linux" && "$arch" == "arm64" ]]; then
        error "Linux ARM64 is not currently supported due to CGO cross-compilation limitations"
    fi

    echo "${os}_${arch}"
}

# Get the latest release version from GitHub
get_latest_version() {
    local response version curl_opts

    # Use GitHub token if available (higher rate limit)
    curl_opts=(-sS)
    if [[ -n "${GITHUB_TOKEN:-}" ]]; then
        curl_opts+=(-H "Authorization: token ${GITHUB_TOKEN}")
    fi

    # Fetch with error handling
    response=$(curl "${curl_opts[@]}" "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" 2>&1)

    # Check for rate limiting
    if echo "$response" | grep -q "API rate limit exceeded"; then
        echo ""
        error "GitHub API rate limit exceeded.

You have a few options:
  1. Wait ~1 hour for the rate limit to reset
  2. Specify a version manually:
     curl -sSL https://raw.githubusercontent.com/${GITHUB_REPO}/main/scripts/install.sh | bash -s -- v0.6.1
  3. Use a GitHub token (set GITHUB_TOKEN environment variable)
  4. Clone and build from source:
     git clone https://github.com/${GITHUB_REPO}.git
     cd claude-mnemonic && make build && make install"
    fi

    # Check for other API errors
    if echo "$response" | grep -q '"message":'; then
        local msg
        msg=$(echo "$response" | grep '"message":' | sed -E 's/.*"message": *"([^"]+)".*/\1/')
        error "GitHub API error: $msg"
    fi

    # Extract version
    version=$(echo "$response" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')

    if [[ -z "$version" ]]; then
        error "Failed to fetch latest version from GitHub. Response: $response"
    fi

    echo "$version"
}

# Download and extract the release
download_release() {
    local version="$1"
    local platform="$2"
    local tmp_dir

    tmp_dir=$(mktemp -d)
    trap 'rm -rf "$tmp_dir"' EXIT

    # Construct download URL (use .zip for Windows, .tar.gz for others)
    local archive_ext="tar.gz"
    if [[ "$platform" == windows_* ]]; then
        archive_ext="zip"
    fi
    local archive_name="claude-mnemonic_${version#v}_${platform}.${archive_ext}"
    local download_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/${archive_name}"

    info "Downloading ${archive_name}..."

    if ! curl -sSL -o "$tmp_dir/release.${archive_ext}" "$download_url"; then
        error "Failed to download release from: $download_url"
    fi

    # Verify download integrity via checksum
    local checksums_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/checksums.txt"
    info "Verifying download integrity..."
    if curl -fsSL -o "$tmp_dir/checksums.txt" "$checksums_url" 2>/dev/null; then
        local expected_hash actual_hash
        expected_hash=$(grep "${archive_name}" "$tmp_dir/checksums.txt" | awk '{print $1}')
        if [[ -z "$expected_hash" ]]; then
            warn "Archive ${archive_name} not found in checksums.txt, skipping verification"
        else
            if command -v shasum &> /dev/null; then
                actual_hash=$(shasum -a 256 "$tmp_dir/release.${archive_ext}" | awk '{print $1}')
            elif command -v sha256sum &> /dev/null; then
                actual_hash=$(sha256sum "$tmp_dir/release.${archive_ext}" | awk '{print $1}')
            else
                warn "No SHA256 tool found (shasum or sha256sum), skipping checksum verification"
                actual_hash=""
            fi
            if [[ -n "$actual_hash" ]]; then
                if [[ "$expected_hash" != "$actual_hash" ]]; then
                    error "Checksum verification failed! Expected: $expected_hash Got: $actual_hash"
                fi
                success "Checksum verified"
            fi
        fi
    else
        warn "No checksum file available, skipping verification"
    fi
    info "Extracting archive..."
    if [[ "$archive_ext" == "zip" ]]; then
        if ! command -v unzip &> /dev/null; then
            error "unzip is required for Windows archives but not installed"
        fi
        if ! unzip -q "$tmp_dir/release.zip" -d "$tmp_dir"; then
            error "Failed to extract archive"
        fi
    else
        if ! tar -xzf "$tmp_dir/release.tar.gz" -C "$tmp_dir"; then
            error "Failed to extract archive"
        fi
    fi

    # Stop existing worker if running
    info "Stopping existing worker (if running)..."
    graceful_stop_worker

    # Create installation directories
    info "Installing to ${INSTALL_DIR}..."
    mkdir -p "$INSTALL_DIR/hooks"
    mkdir -p "$INSTALL_DIR/.claude-plugin"
    mkdir -p "$INSTALL_DIR/commands"

    # Copy binaries (abort on failure — could indicate disk full or permissions issue)
    if ! cp "$tmp_dir/worker" "$INSTALL_DIR/"; then
        error "Failed to copy worker binary to $INSTALL_DIR/"
    fi
    if ! cp "$tmp_dir/mcp-server" "$INSTALL_DIR/"; then
        error "Failed to copy mcp-server binary to $INSTALL_DIR/"
    fi
    if ! cp "$tmp_dir/hooks/"* "$INSTALL_DIR/hooks/"; then
        error "Failed to copy hook binaries to $INSTALL_DIR/hooks/"
    fi

    # Copy plugin configuration
    if ! cp "$tmp_dir/.claude-plugin/"* "$INSTALL_DIR/.claude-plugin/"; then
        error "Failed to copy plugin configuration to $INSTALL_DIR/.claude-plugin/"
    fi

    # Copy slash commands if they exist in the release
    if [[ -d "$tmp_dir/commands" ]]; then
        cp -r "$tmp_dir/commands/"* "$INSTALL_DIR/commands/" 2>/dev/null || true
    fi

    # Make binaries executable
    chmod +x "$INSTALL_DIR/worker"
    chmod +x "$INSTALL_DIR/mcp-server"
    chmod +x "$INSTALL_DIR/hooks/"*

    success "Binaries installed to ${INSTALL_DIR}"
}

# Register the plugin with Claude Code
register_plugin() {
    local version="$1"
    local timestamp
    timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")

    # Ensure directories exist
    mkdir -p "$HOME/.claude/plugins"

    # Clean up old cache versions to prevent stale binaries
    local cache_base
    cache_base=$(dirname "$CACHE_DIR")
    if [[ -d "$cache_base" ]]; then
        info "Cleaning up old cache versions..."
        find "$cache_base" -mindepth 1 -maxdepth 1 -type d ! -name "${version#v}" -exec rm -rf {} \; 2>/dev/null || true
    fi

    mkdir -p "${CACHE_DIR}/${version}"

    # Create JSON files if they don't exist
    [[ ! -f "$PLUGINS_FILE" ]] && echo '{"version": 2, "plugins": {}}' > "$PLUGINS_FILE"
    [[ ! -f "$SETTINGS_FILE" ]] && echo '{}' > "$SETTINGS_FILE"
    [[ ! -f "$MARKETPLACES_FILE" ]] && echo '{}' > "$MARKETPLACES_FILE"

    # Check for jq
    if ! command -v jq &> /dev/null; then
        warn "jq is not installed. Plugin registration requires jq."
        warn "Please install jq: brew install jq (macOS) or apt-get install jq (Linux)"
        warn "Then run: $0 --register-only"
        return 1
    fi

    local cache_path="${CACHE_DIR}/${version}"

    # Copy files to cache directory
    mkdir -p "$cache_path/.claude-plugin"
    mkdir -p "$cache_path/hooks"
    mkdir -p "$cache_path/commands"
    cp -r "$INSTALL_DIR/"* "$cache_path/" 2>/dev/null || true

    # Register in installed_plugins.json
    local plugin_entry
    plugin_entry=$(cat <<EOF
[{
    "scope": "user",
    "installPath": "$cache_path",
    "version": "${version#v}",
    "installedAt": "$timestamp",
    "lastUpdated": "$timestamp",
    "isLocal": true
}]
EOF
)

    jq --arg key "$PLUGIN_KEY" --argjson entry "$plugin_entry" \
        '.plugins[$key] = $entry' "$PLUGINS_FILE" > "${PLUGINS_FILE}.tmp" \
        && mv "${PLUGINS_FILE}.tmp" "$PLUGINS_FILE"

    success "Plugin registered in installed_plugins.json"

    # Enable in settings.json and configure statusline
    local statusline_cmd="$INSTALL_DIR/hooks/statusline"
    local statusline_entry
    statusline_entry=$(cat <<EOF
{
    "type": "command",
    "command": "$statusline_cmd",
    "padding": 0
}
EOF
)

    jq --arg key "$PLUGIN_KEY" --argjson statusline "$statusline_entry" \
        '.enabledPlugins //= {} | .enabledPlugins[$key] = true | .statusLine = $statusline' "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp" \
        && mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE"

    success "Plugin enabled in settings.json"
    success "Statusline configured in settings.json"

    # Register marketplace
    local marketplace_entry
    marketplace_entry=$(cat <<EOF
{
    "source": {
        "source": "directory",
        "path": "$INSTALL_DIR"
    },
    "installLocation": "$INSTALL_DIR",
    "lastUpdated": "$timestamp"
}
EOF
)

    jq --arg key "claude-mnemonic" --argjson entry "$marketplace_entry" \
        '.[$key] = $entry' "$MARKETPLACES_FILE" > "${MARKETPLACES_FILE}.tmp" \
        && mv "${MARKETPLACES_FILE}.tmp" "$MARKETPLACES_FILE"

    success "Marketplace registered in known_marketplaces.json"

    # Register MCP server in settings.json
    local mcp_binary="$INSTALL_DIR/mcp-server"
    if [[ -f "$mcp_binary" ]]; then
        info "Registering MCP server in settings.json..."

        # MCP server entry - note the escaped ${CLAUDE_PROJECT}
        local mcp_entry
        mcp_entry=$(cat <<'EOF'
{
    "command": "MCP_BINARY_PLACEHOLDER",
    "args": ["--project", "${CLAUDE_PROJECT}"],
    "env": {}
}
EOF
)
        # Replace placeholder with actual path
        mcp_entry=$(echo "$mcp_entry" | sed "s|MCP_BINARY_PLACEHOLDER|$mcp_binary|g")

        # Add or update mcpServers field
        if jq --arg key "claude-mnemonic" --argjson entry "$mcp_entry" \
            '.mcpServers //= {} | .mcpServers[$key] = $entry' "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp"; then
            mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE"
            success "MCP server registered successfully"
        else
            warn "Failed to register MCP server (jq error)"
            rm -f "${SETTINGS_FILE}.tmp"
        fi
    else
        warn "MCP server binary not found at $mcp_binary, skipping MCP registration"
    fi
}

# Start the worker service
start_worker() {
    local worker_path="$INSTALL_DIR/worker"

    if [[ ! -x "$worker_path" ]]; then
        error "Worker binary not found at $worker_path"
    fi

    # Check for port conflict with a non-mnemonic process
    if command -v lsof &> /dev/null; then
        local port_pid
        port_pid=$(lsof -ti :37777 2>/dev/null || true)
        if [[ -n "$port_pid" ]]; then
            local port_cmd
            port_cmd=$(ps -p "$port_pid" -o comm= 2>/dev/null || true)
            if [[ -n "$port_cmd" ]] && ! echo "$port_cmd" | grep -q "worker"; then
                warn "Port 37777 is in use by another process: $port_cmd (PID $port_pid)"
                warn "The worker may fail to start. Consider stopping the conflicting process."
            fi
        fi
    fi

    info "Starting worker service..."
    nohup "$worker_path" > /tmp/claude-mnemonic-worker.log 2>&1 &

    # Retry health check up to 5 times with 1s interval
    local retries=0
    local max_retries=5
    while [[ $retries -lt $max_retries ]]; do
        sleep 1
        if curl -sS http://localhost:37777/health > /dev/null 2>&1; then
            success "Worker started successfully at http://localhost:37777"
            return 0
        fi
        retries=$((retries + 1))
    done

    warn "Worker may not have started properly after ${max_retries} attempts. Check /tmp/claude-mnemonic-worker.log"
}

# Check optional dependencies for semantic search
check_optional_deps() {
    # Semantic search uses embedded ONNX runtime - no external Python/uvx dependencies needed
    success "Semantic search enabled (embedded ONNX runtime)"
}

# Rollback partially installed files on failure
INSTALL_COMPLETE=false
cleanup_on_failure() {
    if [[ "$INSTALL_COMPLETE" != "true" ]]; then
        warn "Installation did not complete — cleaning up partial install..."
        rm -rf "$INSTALL_DIR" 2>/dev/null || true
        rm -rf "$CACHE_DIR" 2>/dev/null || true
    fi
}

# Main installation flow
main() {
    local version="${1:-}"

    trap cleanup_on_failure EXIT

    echo ""
    echo "╔═══════════════════════════════════════════════════════════╗"
    echo "║           Claude Mnemonic - Installation Script           ║"
    echo "║     Persistent Memory System for Claude Code CLI          ║"
    echo "╚═══════════════════════════════════════════════════════════╝"
    echo ""

    # Check required dependencies
    if ! command -v curl &> /dev/null; then
        error "curl is required but not installed"
    fi

    if ! command -v tar &> /dev/null; then
        error "tar is required but not installed"
    fi

    # Detect platform
    local platform
    platform=$(detect_platform)
    info "Detected platform: $platform"

    # Get version
    if [[ -z "$version" ]]; then
        info "Fetching latest release..."
        version=$(get_latest_version)
    fi
    info "Installing version: $version"

    # Download and install
    download_release "$version" "$platform"

    # Register plugin
    if register_plugin "$version"; then
        success "Plugin registered successfully"
    else
        warn "Plugin registration incomplete - please install jq and run again"
    fi

    # Start worker
    start_worker

    # Check optional dependencies
    check_optional_deps

    INSTALL_COMPLETE=true

    echo ""
    echo "╔═══════════════════════════════════════════════════════════╗"
    echo "║                  Installation Complete!                   ║"
    echo "╠═══════════════════════════════════════════════════════════╣"
    echo "║  Dashboard: http://localhost:37777                        ║"
    echo "║  Logs: /tmp/claude-mnemonic-worker.log                    ║"
    echo "║                                                           ║"
    echo "║  Start a new Claude Code CLI session to activate memory.  ║"
    echo "╚═══════════════════════════════════════════════════════════╝"
    echo ""
}

# Handle --version flag
if [[ "${1:-}" == "--version" ]]; then
    echo "claude-mnemonic installer v${INSTALLER_VERSION}"
    exit 0
fi

# Handle --register-only flag
if [[ "${1:-}" == "--register-only" ]]; then
    version=$(cat "$INSTALL_DIR/.claude-plugin/plugin.json" 2>/dev/null | grep '"version"' | sed -E 's/.*"([^"]+)".*/\1/' || echo "1.0.0")
    register_plugin "v$version"
    exit 0
fi

# Handle --uninstall flag
if [[ "${1:-}" == "--uninstall" ]]; then
    KEEP_DATA=false
    [[ "${2:-}" == "--keep-data" ]] && KEEP_DATA=true

    echo ""
    echo "╔═══════════════════════════════════════════════════════════╗"
    echo "║         Claude Mnemonic - Uninstallation                  ║"
    echo "╚═══════════════════════════════════════════════════════════╝"
    echo ""

    info "Stopping worker processes..."
    graceful_stop_worker

    info "Removing plugin directories..."
    rm -rf "$INSTALL_DIR"
    rm -rf "$CACHE_DIR"
    success "Plugin directories removed"

    # Remove from JSON files (if jq is available)
    if command -v jq &> /dev/null; then
        info "Cleaning up Claude Code configuration..."
        if [[ -f "$PLUGINS_FILE" ]]; then
            jq 'del(.plugins["'"$PLUGIN_KEY"'"])' "$PLUGINS_FILE" > "${PLUGINS_FILE}.tmp" && mv "${PLUGINS_FILE}.tmp" "$PLUGINS_FILE"
        fi
        if [[ -f "$SETTINGS_FILE" ]]; then
            # Remove plugin from enabled plugins, remove statusline if it's ours, and remove MCP server entry
            jq 'del(.enabledPlugins["'"$PLUGIN_KEY"'"]) |
                if .statusLine.command | test("claude-mnemonic") then del(.statusLine) else . end |
                del(.mcpServers["claude-mnemonic"])' "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp" && mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE"
        fi
        if [[ -f "$MARKETPLACES_FILE" ]]; then
            jq 'del(.["claude-mnemonic"])' "$MARKETPLACES_FILE" > "${MARKETPLACES_FILE}.tmp" && mv "${MARKETPLACES_FILE}.tmp" "$MARKETPLACES_FILE"
        fi
        success "Configuration cleaned up"
    else
        warn "jq not found - configuration files not cleaned up"
    fi

    # Handle data directory
    DATA_DIR="$HOME/.claude-mnemonic"
    if [[ -d "$DATA_DIR" ]]; then
        if [[ "$KEEP_DATA" == "true" ]]; then
            warn "Keeping data directory: $DATA_DIR"
        else
            info "Removing data directory..."
            rm -rf "$DATA_DIR"
            success "Data directory removed"
        fi
    fi

    echo ""
    success "Claude Mnemonic uninstalled successfully"
    exit 0
fi

main "$@"
