//go:build !windows

package update

import (
	"fmt"
	"os"
	"path/filepath"
)

// Apply replaces the current executable with the downloaded binary.
func Apply(binaryPath string) error {
	// Get path to current executable.
	exe, err := os.Executable()
	if err != nil {
		return fmt.Errorf("failed to get executable path: %w", err)
	}

	// Resolve symlinks.
	exe, err = filepath.EvalSymlinks(exe)
	if err != nil {
		return fmt.Errorf("failed to resolve symlinks: %w", err)
	}

	// Get the directory of the executable.
	exeDir := filepath.Dir(exe)

	// Check if we have write permissions to the directory.
	if err := checkWritePermission(exeDir); err != nil {
		return fmt.Errorf("cannot write to %s: %w (you may need to run with elevated privileges)", exeDir, err)
	}

	// Copy binary to exe directory first to ensure same filesystem.
	// os.Rename fails across filesystems, and binaryPath may be in /tmp.
	localBinary := filepath.Join(exeDir, ".crush-update-new")
	if err := copyFile(binaryPath, localBinary); err != nil {
		return fmt.Errorf("failed to copy new binary: %w", err)
	}

	// Create a backup of the current executable.
	backupPath := filepath.Join(exeDir, filepath.Base(exe)+".old")
	if err := os.Rename(exe, backupPath); err != nil {
		os.Remove(localBinary)
		return fmt.Errorf("failed to backup current executable: %w", err)
	}

	// Move new binary to executable location.
	if err := os.Rename(localBinary, exe); err != nil {
		// Try to restore backup on failure.
		_ = os.Rename(backupPath, exe)
		os.Remove(localBinary)
		return fmt.Errorf("failed to install new version: %w", err)
	}

	// Remove backup on success.
	_ = os.Remove(backupPath)

	return nil
}

// HasPendingUpdate returns false on Unix since updates are applied immediately.
func HasPendingUpdate() bool {
	return false
}

// ApplyPendingUpdate is a no-op on Unix since updates are applied immediately.
func ApplyPendingUpdate() error {
	return nil
}
