#!/usr/bin/env bash
# Terragrunt Installer
#
# Supported platforms: Linux, macOS (Darwin)
# Supported architectures: x86_64 (amd64), aarch64/arm64, i386/i686 (386)
# Requirements: bash 3.2+, curl, sha256sum or shasum
#
# Note: This script requires bash (not sh) for:
#   - pipefail option (set -o pipefail)
#   - local variables in functions
#   - [[ ]] test syntax
#   - arrays and readonly declarations
#
# Usage:
#   curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash
#   curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- -v v0.72.5
#   curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- -d ~/bin
#   curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --tip
#   curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --test --commit abc123def
#
# Options:
#   -v, --version VERSION    Install specific version (default: latest)
#   -d, --dir PATH           Installation directory (default: ~/.terragrunt/bin)
#   -f, --force              Overwrite existing installation
#   --tip                    Install the latest tip build (from main branch)
#   --test                   Install a test build (requires --commit)
#   --commit SHA             Specify commit SHA for --tip or --test builds
#   --verify-cosign          Use Cosign instead of GPG for signature verification
#   --no-verify-sig          Skip GPG/Cosign signature verification
#   --no-verify-attestation  Skip GitHub release attestation verification
#   --no-verify              Skip SHA256 checksum verification
#   -h, --help               Show this help message
#
# Signature verification (GPG) is enabled by default for versions >= v0.98.0.
# Use --no-verify-sig to skip, or --verify-cosign to use Cosign instead of GPG.
#
# Release attestation verification is enabled by default for versions >= v1.1.0,
# which are published as immutable releases. It checks the downloaded assets
# against the release attestation using the GitHub CLI (gh) v2.81.0+, and is
# skipped with a warning when gh is unavailable or unauthenticated.
# Use --no-verify-attestation to skip.
#
# Environment:
#   TERRAGRUNT_VERSION       Override version (same as -v)
#   TERRAGRUNT_INSTALL_DIR   Override install directory (same as -d)

set -euo pipefail

# --- Constants ---
readonly GITHUB_REPO="gruntwork-io/terragrunt"
readonly GPG_KEY_URL="https://gruntwork.io/.well-known/pgp-key.txt"
readonly DEFAULT_INSTALL_DIR="${HOME}/.terragrunt/bin"
readonly BINARY_NAME="terragrunt"
# Minimum version that has signed release assets (GPG and Cosign)
readonly MIN_SIGNED_VERSION="0.98.0"
# Minimum version published as an immutable release with a release attestation
readonly MIN_ATTESTED_VERSION="1.1.0"
# Minimum GitHub CLI version that supports 'gh release verify-asset'
readonly MIN_GH_ATTESTATION_VERSION="2.81.0"
# Builds API base URL for tip/test builds
readonly BUILDS_API_BASE="https://builds.terragrunt.com"

# --- Colors (if terminal) ---
# Use $'...' syntax for reliable escape sequence interpretation on macOS/Linux
if [[ -t 1 ]]; then
    readonly RED=$'\033[0;31m'
    readonly GREEN=$'\033[0;32m'
    readonly YELLOW=$'\033[0;33m'
    readonly BLUE=$'\033[0;34m'
    readonly NC=$'\033[0m' # No Color
else
    readonly RED=''
    readonly GREEN=''
    readonly YELLOW=''
    readonly BLUE=''
    readonly NC=''
fi

# --- Helper Functions ---
abort() {
    printf "${RED}Error: %s${NC}\n" "$1" >&2
    exit 1
}

info() {
    printf "${BLUE}==> ${NC}%s\n" "$1"
}

warn() {
    printf "${YELLOW}Warning: %s${NC}\n" "$1" >&2
}

success() {
    printf "${GREEN}==> %s${NC}\n" "$1"
}

usage() {
    cat <<EOF
Terragrunt Installer

Usage:
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- [OPTIONS]

Options:
  -v, --version VERSION    Install specific version (default: latest)
  -d, --dir PATH           Installation directory (default: ~/.terragrunt/bin)
  -f, --force              Overwrite existing installation
  --tip                    Install the latest tip build (from main branch)
  --test                   Install a test build (requires --commit)
  --commit SHA             Specify commit SHA for --tip or --test builds
  --verify-cosign          Use Cosign instead of GPG for signature verification
  --no-verify-sig          Skip GPG/Cosign signature verification
  --no-verify-attestation  Skip GitHub release attestation verification
  --no-verify              Skip SHA256 checksum verification
  -h, --help               Show this help message

Signature verification (GPG) is enabled by default for versions >= v0.98.0.
Use --no-verify-sig to skip, or --verify-cosign to use Cosign instead of GPG.

Release attestation verification is enabled by default for versions >= v1.1.0,
which are published as immutable releases. It requires an authenticated
GitHub CLI (gh) v2.81.0+ and is skipped with a warning when unavailable.
Use --no-verify-attestation to skip.

Examples:
  # Install latest version
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash

  # Install specific version
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- -v v0.98.0

  # Install to custom directory
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- -d ~/bin

  # Install without signature verification
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --no-verify-sig

  # Install using Cosign instead of GPG
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --verify-cosign

  # Install the latest tip build
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --tip

  # Install a specific tip build by commit
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --tip --commit abc123def

  # Install a test build by commit
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --test --commit abc123def
EOF
}

# --- Version Comparison ---
# Compare two semantic versions. Returns 0 if $1 >= $2, 1 otherwise.
version_gte() {
    local version=$1
    local min_version=$2

    # Strip 'v' prefix if present
    version="${version#v}"
    min_version="${min_version#v}"

    # Use sort -V if available, otherwise fall back to manual comparison
    if echo | sort -V >/dev/null 2>&1; then
        local sorted_first
        sorted_first=$(printf '%s\n%s' "$min_version" "$version" | sort -V | head -n1)
        [[ "$sorted_first" == "$min_version" ]]
    else
        # Manual version comparison for systems without sort -V (e.g., older macOS)
        local i
        local IFS='.'
        read -ra v1 <<<"$version"
        read -ra v2 <<<"$min_version"

        for ((i = 0; i < ${#v2[@]}; i++)); do
            local n1=${v1[i]:-0}
            local n2=${v2[i]:-0}
            if ((n1 > n2)); then
                return 0
            elif ((n1 < n2)); then
                return 1
            fi
        done
        return 0
    fi
}

# Check if version supports signature verification
supports_signature_verification() {
    local version=$1
    version_gte "$version" "$MIN_SIGNED_VERSION"
}

# Check if version is published as an immutable release with a release attestation
supports_attestation_verification() {
    local version=$1
    version_gte "$version" "$MIN_ATTESTED_VERSION"
}

# Check if an installed GitHub CLI supports 'gh release verify-asset'
gh_supports_attestation_verification() {
    command -v gh &>/dev/null || return 1

    local gh_version
    gh_version=$(gh --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1)
    [[ -n "$gh_version" ]] || return 1

    version_gte "$gh_version" "$MIN_GH_ATTESTATION_VERSION"
}

# --- OS/Arch Detection ---
detect_os() {
    local os
    os="$(uname -s)"
    case "$os" in
    Darwin) echo "darwin" ;;
    Linux) echo "linux" ;;
    MINGW* | MSYS* | CYGWIN*)
        abort "Windows detected. Please use PowerShell or install via Chocolatey:
  choco install terragrunt

Or download manually from: https://github.com/gruntwork-io/terragrunt/releases"
        ;;
    *)
        abort "Unsupported operating system: $os
Supported: Linux, macOS (Darwin)"
        ;;
    esac
}

detect_arch() {
    local arch
    arch="$(uname -m)"
    case "$arch" in
    x86_64 | amd64) echo "amd64" ;;
    aarch64 | arm64) echo "arm64" ;;
    i386 | i686) echo "386" ;;
    *)
        abort "Unsupported architecture: $arch
Supported: x86_64 (amd64), aarch64 (arm64), i386/i686 (386)"
        ;;
    esac
}

# --- Version Resolution ---
get_latest_version() {
    local version

    # Method 1: Use redirect URL (higher rate limits than API)
    # GitHub redirects /releases/latest to /releases/tag/vX.Y.Z
    local redirect_url
    if redirect_url=$(curl -fsI "https://github.com/${GITHUB_REPO}/releases/latest" 2>/dev/null | grep -i '^location:' | tr -d '\r'); then
        version=$(echo "$redirect_url" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1)
        if [[ -n "$version" ]]; then
            echo "$version"
            return 0
        fi
    fi

    # Method 2: Fallback to GitHub API (may hit rate limits: 60 req/hour unauthenticated)
    if version=$(curl -fsL "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" 2>/dev/null | grep -o '"tag_name": "[^"]*' | cut -d'"' -f4); then
        if [[ -n "$version" ]]; then
            echo "$version"
            return 0
        fi
    fi

    abort "Could not determine latest version.
This may be due to GitHub API rate limits (60 requests/hour).
Specify a version manually with -v, e.g.: -v v0.72.5"
}

validate_version() {
    local version="$1"
    # Allow any version/tag (semver, release candidates, custom builds)
    [[ -z "$version" ]] && abort "Version cannot be empty"
    echo "$version"
}

# --- Download Functions ---
download_file() {
    local url="$1"
    local output="$2"
    local description="$3"

    info "Downloading $description..."
    if ! curl -sL --fail "$url" -o "$output" 2>/dev/null; then
        abort "Failed to download $description from: $url"
    fi
}

download_binary() {
    local version="$1"
    local binary_name="$2"
    local output_dir="$3"

    local url="https://github.com/${GITHUB_REPO}/releases/download/${version}/${binary_name}"
    download_file "$url" "${output_dir}/${binary_name}" "Terragrunt ${version}"
}

download_checksums() {
    local version="$1"
    local output_dir="$2"

    local url="https://github.com/${GITHUB_REPO}/releases/download/${version}/SHA256SUMS"
    download_file "$url" "${output_dir}/SHA256SUMS" "checksums"
}

# --- Tip/Test Build Functions ---

get_build_file_url() {
    local channel="$1"
    local commit="$2"
    local os="$3"
    local arch="$4"
    local filename="$5"

    if [[ "$channel" == "tip" ]]; then
        local ref="$commit"

        [[ -z "$ref" ]] && ref="latest"
        local base="${BUILDS_API_BASE}/api/v1/tip/${ref}/download"
        if [[ -n "$filename" ]]; then
            echo "${base}?filename=${filename}"
            return
        fi

        echo "${base}?os=${os}&arch=${arch}"

        return
    fi

    local archive_name="terragrunt_${os}_${arch}.tar.gz"

    if [[ -n "$filename" ]]; then
        echo "${BUILDS_API_BASE}/test/${commit}/${filename}"

        return
    fi

    echo "${BUILDS_API_BASE}/test/${commit}/${archive_name}"
}

download_build_archive() {
    local channel="$1"
    local commit="$2"
    local os="$3"
    local arch="$4"
    local output_dir="$5"

    local archive_name="terragrunt_${os}_${arch}.tar.gz"

    local url
    url=$(get_build_file_url "$channel" "$commit" "$os" "$arch" "")

    download_file "$url" "${output_dir}/${archive_name}" "Terragrunt ${channel} build"
}

download_build_checksums() {
    local channel="$1"
    local commit="$2"
    local os="$3"
    local arch="$4"
    local output_dir="$5"

    local url
    url=$(get_build_file_url "$channel" "$commit" "$os" "$arch" "SHA256SUMS")

    download_file "$url" "${output_dir}/SHA256SUMS" "checksums"
}

download_build_signature_files() {
    local channel="$1"
    local commit="$2"
    local os="$3"
    local arch="$4"
    local tmpdir="$5"
    local method="$6"

    local url
    case "$method" in
    gpg)
        url=$(get_build_file_url "$channel" "$commit" "$os" "$arch" "SHA256SUMS.gpgsig")
        download_file "$url" "${tmpdir}/SHA256SUMS.gpgsig" "GPG signature"
        ;;
    cosign)
        url=$(get_build_file_url "$channel" "$commit" "$os" "$arch" "SHA256SUMS.sigstore.json")
        download_file "$url" "${tmpdir}/SHA256SUMS.sigstore.json" "Cosign signature bundle"
        ;;
    esac
}

extract_archive() {
    local archive_path="$1"
    local output_dir="$2"
    local binary_name="$3"

    info "Extracting archive..."

    if ! tar -xzf "$archive_path" -C "$output_dir" 2>/dev/null; then
        abort "Failed to extract archive: $archive_path"
    fi

    if [[ ! -f "${output_dir}/${binary_name}" ]]; then
        abort "Expected binary '${binary_name}' not found in archive"
    fi
}

# --- Verification Functions ---
verify_sha256() {
    local binary_path="$1"
    local checksums_path="$2"
    local binary_name="$3"

    info "Verifying SHA256 checksum..."

    local actual_checksum
    if command -v sha256sum &>/dev/null; then
        actual_checksum=$(sha256sum "$binary_path" | awk '{print $1}')
    elif command -v shasum &>/dev/null; then
        actual_checksum=$(shasum -a 256 "$binary_path" | awk '{print $1}')
    else
        abort "Neither sha256sum nor shasum found. Cannot verify checksum."
    fi

    local expected_checksum
    # Strip CRLF and find checksum for binary
    expected_checksum=$(tr -d '\r' <"$checksums_path" | awk -v bin="$binary_name" '$2 == bin {print $1; exit}')

    if [[ -z "$expected_checksum" ]]; then
        abort "Could not find checksum for $binary_name in SHA256SUMS file"
    fi

    if [[ "$actual_checksum" != "$expected_checksum" ]]; then
        abort "Checksum verification failed!
Expected: $expected_checksum
Got:      $actual_checksum

The downloaded file may be corrupted or tampered with."
    fi
}

verify_gpg() {
    local version="$1"
    local checksums_path="$2"
    local tmpdir="$3"

    local sig_path="${tmpdir}/SHA256SUMS.gpgsig"
    local gnupg_home="${tmpdir}/gnupg"

    # Download signature file if not already present (tip/test builds pre-download it)
    if [[ ! -f "$sig_path" ]]; then
        local sig_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/SHA256SUMS.gpgsig"
        info "Downloading GPG signature..."
        if ! curl -sL --fail "$sig_url" -o "$sig_path" 2>/dev/null; then
            warn "Failed to download GPG signature file"
            return 1
        fi
    fi

    # Create temporary GNUPGHOME to avoid polluting user's keyring
    mkdir -p "$gnupg_home"
    chmod 700 "$gnupg_home"

    info "Importing Gruntwork GPG key..."
    if ! curl -sL "$GPG_KEY_URL" | GNUPGHOME="$gnupg_home" gpg --import 2>/dev/null; then
        warn "Failed to import GPG key"
        return 1
    fi

    info "Verifying GPG signature..."
    if GNUPGHOME="$gnupg_home" gpg --verify "$sig_path" "$checksums_path" 2>/dev/null; then
        return 0
    fi
    return 1
}

verify_cosign() {
    local version="$1"
    local checksums_path="$2"
    local tmpdir="$3"

    # Try bundle verification first (cosign v3+ / sigstore bundle)
    local bundle_path="${tmpdir}/SHA256SUMS.sigstore.json"

    # Download bundle if not already present (tip/test builds pre-download it)
    if [[ ! -f "$bundle_path" ]]; then
        local bundle_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/SHA256SUMS.sigstore.json"
        curl -sL --fail "$bundle_url" -o "$bundle_path" 2>/dev/null || true
    fi

    if [[ -f "$bundle_path" ]]; then
        info "Verifying Cosign signature (bundle)..."
        cosign verify-blob "$checksums_path" \
            --bundle "$bundle_path" \
            --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
            --certificate-identity-regexp "github.com/gruntwork-io/terragrunt" 2>/dev/null
        return $?
    fi

    # Legacy .sig/.pem verification (older releases without bundle)
    local sig_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/SHA256SUMS.sig"
    local cert_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/SHA256SUMS.pem"
    local sig_path="${tmpdir}/SHA256SUMS.sig"
    local cert_path="${tmpdir}/SHA256SUMS.pem"

    info "Downloading Cosign signature files..."
    curl -sL --fail "$sig_url" -o "$sig_path" 2>/dev/null || {
        warn "Failed to download Cosign signature file"
        return 1
    }
    curl -sL --fail "$cert_url" -o "$cert_path" 2>/dev/null || {
        warn "Failed to download Cosign certificate file"
        return 1
    }

    info "Verifying Cosign signature..."
    cosign verify-blob "$checksums_path" \
        --signature "$sig_path" \
        --certificate "$cert_path" \
        --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
        --certificate-identity-regexp "github.com/gruntwork-io/terragrunt" 2>/dev/null
    return $?
}

# Verify signature using specified method
verify_signature() {
    local version="$1"
    local checksums_path="$2"
    local tmpdir="$3"
    local method="$4" # gpg or cosign

    case "$method" in
    gpg)
        command -v gpg &>/dev/null || abort "GPG verification requested but gpg is not installed"
        verify_gpg "$version" "$checksums_path" "$tmpdir" && return 0
        abort "GPG signature verification failed!"
        ;;
    cosign)
        command -v cosign &>/dev/null || abort "Cosign verification requested but cosign is not installed"
        verify_cosign "$version" "$checksums_path" "$tmpdir" && return 0
        abort "Cosign signature verification failed!"
        ;;
    esac
}

# Verify downloaded assets against the GitHub release attestation.
# Skips with a warning when the release predates attestations or when a
# capable, authenticated GitHub CLI is not available.
verify_release_attestation() {
    local version="$1"
    local tmpdir="$2"
    local binary_name="$3"

    if ! supports_attestation_verification "$version"; then
        warn "Skipping release attestation verification: not available for versions older than v${MIN_ATTESTED_VERSION}"
        return 0
    fi

    if ! gh_supports_attestation_verification; then
        warn "Skipping release attestation verification: requires GitHub CLI (gh) v${MIN_GH_ATTESTATION_VERSION} or later"
        return 0
    fi

    if ! gh auth status &>/dev/null; then
        warn "Skipping release attestation verification: GitHub CLI (gh) is not authenticated"
        return 0
    fi

    info "Verifying release attestation..."

    local asset
    for asset in "SHA256SUMS" "$binary_name"; do
        if ! gh release verify-asset "$version" "${tmpdir}/${asset}" --repo "$GITHUB_REPO" >/dev/null 2>&1; then
            abort "Release attestation verification failed for ${asset}!

The downloaded file does not match the asset recorded in the ${version} release attestation.
It may be corrupted or tampered with."
        fi
    done

    success "Release attestation verified"
}

# --- Shell RC Detection ---
detect_shell_rc() {
    local shell_name
    shell_name=$(basename "${SHELL:-}")
    case "$shell_name" in
    bash)
        if [[ -f "${HOME}/.bashrc" ]]; then
            echo "${HOME}/.bashrc"
        elif [[ -f "${HOME}/.bash_profile" ]]; then
            echo "${HOME}/.bash_profile"
        fi
        ;;
    zsh)
        echo "${HOME}/.zshrc"
        ;;
    fish)
        echo "${HOME}/.config/fish/config.fish"
        ;;
    esac
}

# Check if PATH already contains install dir
path_already_configured() {
    local install_dir="$1"
    local rc_file
    rc_file=$(detect_shell_rc)

    [[ -n "$rc_file" ]] && grep -Fq "${install_dir}" "$rc_file" 2>/dev/null
}

# --- Installation ---
install_binary() {
    local binary_path="$1"
    local install_dir="$2"
    local force="$3"
    local requested_version="$4"
    local target_path="${install_dir}/${BINARY_NAME}"

    # Check if already exists (skip if force)
    if [[ -f "$target_path" && "$force" != "true" ]]; then
        # For tip/test builds, version comparison is unreliable — just require --force
        [[ -n "$BUILD_CHANNEL" ]] &&
            abort "Terragrunt is already installed at $target_path
Use --force to overwrite with ${requested_version}"

        local existing_version
        existing_version=$("$target_path" --version 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -n 1 || echo "unknown")

        [[ "$existing_version" == "$requested_version" ]] &&
            abort "Terragrunt ${existing_version} is already installed at $target_path"

        abort "A different version (${existing_version}) is installed at $target_path
Use --force to upgrade/downgrade to ${requested_version}"
    fi

    # Create install directory if needed
    [[ ! -d "$install_dir" ]] && {
        info "Creating installation directory: $install_dir"
        mkdir -p "$install_dir" 2>/dev/null || abort "Failed to create installation directory: $install_dir"
    }

    # Check write permissions
    [[ ! -w "$install_dir" ]] && abort "Cannot write to $install_dir
Run with sudo:
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | sudo bash
Or specify a different directory:
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- -d ~/bin"

    info "Installing to ${target_path}..."
    install -m 0755 "$binary_path" "$target_path"
}

# --- Argument Parsing ---
parse_args() {
    # Set defaults from environment or hardcoded values
    VERSION="${TERRAGRUNT_VERSION:-}"
    INSTALL_DIR="${TERRAGRUNT_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
    VERIFY_SHA=true
    VERIFY_SIG="gpg"         # gpg (default), cosign, or empty (via --no-verify-sig)
    SKIP_SIG_VERIFY=false    # set by --no-verify-sig to disable signature verification
    VERIFY_ATTESTATION=true  # set to false by --no-verify-attestation
    FORCE=false
    BUILD_CHANNEL="" # "", "tip", or "test"
    COMMIT_SHA=""    # commit SHA for --commit

    while [[ $# -gt 0 ]]; do
        case "$1" in
        -v | --version)
            [[ -z "${2:-}" ]] && abort "Option $1 requires a version argument"
            VERSION="$2"
            shift 2
            ;;
        -d | --dir)
            [[ -z "${2:-}" ]] && abort "Option $1 requires a directory argument"
            INSTALL_DIR="$2"
            shift 2
            ;;
        -f | --force)
            FORCE=true
            shift
            ;;
        --tip)
            [[ -n "$BUILD_CHANNEL" ]] && abort "--tip and --test are mutually exclusive"
            BUILD_CHANNEL="tip"
            shift
            ;;
        --test)
            [[ -n "$BUILD_CHANNEL" ]] && abort "--tip and --test are mutually exclusive"
            BUILD_CHANNEL="test"
            shift
            ;;
        --commit)
            [[ -z "${2:-}" ]] && abort "Option $1 requires a commit SHA argument"
            COMMIT_SHA="$2"
            shift 2
            ;;
        --verify-cosign)
            VERIFY_SIG="cosign"
            shift
            ;;
        --no-verify-sig)
            SKIP_SIG_VERIFY=true
            shift
            ;;
        --no-verify-attestation)
            VERIFY_ATTESTATION=false
            shift
            ;;
        --no-verify)
            VERIFY_SHA=false
            shift
            ;;
        -h | --help)
            usage
            exit 0
            ;;
        -*)
            abort "Unknown option: $1
Use -h or --help for usage information"
            ;;
        *)
            abort "Unexpected argument: $1
Use -h or --help for usage information"
            ;;
        esac
    done

    # Validate tip/test/commit flag combinations
    if [[ -n "$BUILD_CHANNEL" && -n "$VERSION" ]]; then
        abort "--${BUILD_CHANNEL} and --version are mutually exclusive"
    fi

    if [[ "$BUILD_CHANNEL" == "test" && -z "$COMMIT_SHA" ]]; then
        abort "--test requires --commit <sha> to specify which test build to install"
    fi

    if [[ -n "$COMMIT_SHA" && -z "$BUILD_CHANNEL" ]]; then
        abort "--commit requires --tip or --test"
    fi

    if [[ -n "$COMMIT_SHA" && ! "$COMMIT_SHA" =~ ^[0-9a-fA-F]{7,40}$ ]]; then
        abort "Invalid commit SHA: $COMMIT_SHA
Commit SHA must be 7-40 hexadecimal characters"
    fi
}

# --- Dependency Check ---
check_dependencies() {
    command -v curl &>/dev/null || abort "curl is required but not installed.
Please install curl and try again."

    [[ "$VERIFY_SHA" == true ]] && ! command -v sha256sum &>/dev/null && ! command -v shasum &>/dev/null &&
        abort "Neither sha256sum nor shasum found.
Install one of these tools or skip checksum verification with:
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --no-verify"

    # Tip/test builds require tar for archive extraction
    [[ -n "$BUILD_CHANNEL" ]] && ! command -v tar &>/dev/null &&
        abort "tar is required to extract tip/test build archives but is not installed."

    # Handle signature verification setup
    [[ "$SKIP_SIG_VERIFY" == true ]] && {
        VERIFY_SIG=""
        return
    }

    # Verify required tool is available
    case "$VERIFY_SIG" in
    gpg)
        command -v gpg &>/dev/null || abort "GPG signature verification requires gpg but it is not installed.
Install gpg or skip signature verification with:
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --no-verify-sig
Or use Cosign instead:
  curl -sSfL --proto '=https' --tlsv1.2 https://terragrunt.com/install | bash -s -- --verify-cosign"
        ;;
    cosign)
        command -v cosign &>/dev/null || abort "Cosign verification requested but cosign is not installed."
        ;;
    esac
}

# --- Main ---

# Install a tip or test build from builds.terragrunt.com
install_build() {
    local os="$1"
    local arch="$2"

    local commit="${COMMIT_SHA:-latest}"
    local archive_name="terragrunt_${os}_${arch}.tar.gz"
    local binary_name="terragrunt_${os}_${arch}"

    local display_version="${BUILD_CHANNEL} (${commit})"

    info "Installing Terragrunt ${display_version} for ${os}/${arch}"

    # Create temp directory with safe cleanup
    local tmpdir
    tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t 'terragrunt-install')
    trap '[[ -n "${tmpdir:-}" && -d "${tmpdir:-}" ]] && rm -rf "$tmpdir"' EXIT

    # Download archive and checksums
    download_build_archive "$BUILD_CHANNEL" "$commit" "$os" "$arch" "$tmpdir"
    download_build_checksums "$BUILD_CHANNEL" "$commit" "$os" "$arch" "$tmpdir"

    # Verify signature first (all tip/test builds are signed)
    if [[ -n "$VERIFY_SIG" ]]; then
        download_build_signature_files "$BUILD_CHANNEL" "$commit" "$os" "$arch" "$tmpdir" "$VERIFY_SIG"
        verify_signature "" "$tmpdir/SHA256SUMS" "$tmpdir" "$VERIFY_SIG"
        success "Signature verified"
    fi

    # Verify checksum (validates archive against authenticated checksums)
    if [[ "$VERIFY_SHA" == true ]]; then
        verify_sha256 "$tmpdir/$archive_name" "$tmpdir/SHA256SUMS" "$archive_name"
        success "SHA256 checksum verified"
    fi
    [[ "$VERIFY_SHA" != true ]] && warn "Skipping checksum verification (--no-verify specified)"

    # Extract binary from archive
    extract_archive "$tmpdir/$archive_name" "$tmpdir" "$binary_name"

    # Install
    install_binary "$tmpdir/$binary_name" "$INSTALL_DIR" "$FORCE" "$display_version"

    local target_path="${INSTALL_DIR}/${BINARY_NAME}"
    success "Terragrunt ${display_version} installed successfully to ${target_path}"
}

# Install a release build from GitHub Releases
install_release() {
    local os="$1"
    local arch="$2"

    local version binary_name
    if [[ -z "$VERSION" ]]; then
        info "Fetching latest version..."
        version=$(get_latest_version)
    fi
    [[ -n "$VERSION" ]] && version=$(validate_version "$VERSION")

    binary_name="terragrunt_${os}_${arch}"

    info "Installing Terragrunt ${version} for ${os}/${arch}"

    # Create temp directory with safe cleanup
    local tmpdir
    tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t 'terragrunt-install')
    trap '[[ -n "${tmpdir:-}" && -d "${tmpdir:-}" ]] && rm -rf "$tmpdir"' EXIT

    # Download files
    download_binary "$version" "$binary_name" "$tmpdir"
    download_checksums "$version" "$tmpdir"

    # Verify release attestation first (binds downloaded assets to the immutable release)
    if [[ "$VERIFY_ATTESTATION" == true ]]; then
        verify_release_attestation "$version" "$tmpdir" "$binary_name"
    fi

    # Verify signature (authenticates SHA256SUMS file)
    if [[ -n "$VERIFY_SIG" ]]; then
        if supports_signature_verification "$version"; then
            verify_signature "$version" "$tmpdir/SHA256SUMS" "$tmpdir" "$VERIFY_SIG"
            success "Signature verified"
        fi
        ! supports_signature_verification "$version" &&
            warn "Skipping signature verification: not available for versions older than v${MIN_SIGNED_VERSION}"
    fi

    # Verify checksum (validates binary against authenticated checksums)
    if [[ "$VERIFY_SHA" == true ]]; then
        verify_sha256 "$tmpdir/$binary_name" "$tmpdir/SHA256SUMS" "$binary_name"
        success "SHA256 checksum verified"
    fi
    [[ "$VERIFY_SHA" != true ]] && warn "Skipping checksum verification (--no-verify specified)"

    # Install
    install_binary "$tmpdir/$binary_name" "$INSTALL_DIR" "$FORCE" "$version"

    local target_path="${INSTALL_DIR}/${BINARY_NAME}"
    success "Terragrunt ${version} installed successfully to ${target_path}"
}

main() {
    parse_args "$@"

    # Expand tilde in INSTALL_DIR (bash doesn't expand ~ in quoted variables)
    INSTALL_DIR="${INSTALL_DIR/#\~/$HOME}"

    # Check dependencies
    check_dependencies

    # Detect platform
    local os arch
    os=$(detect_os)
    arch=$(detect_arch)

    # Install tip/test build or release
    if [[ -n "$BUILD_CHANNEL" ]]; then
        install_build "$os" "$arch"
    fi
    [[ -z "$BUILD_CHANNEL" ]] && install_release "$os" "$arch"

    echo ""

    # Show PATH instructions if using default dir and not already configured
    if [[ "$INSTALL_DIR" == "$DEFAULT_INSTALL_DIR" ]] && ! path_already_configured "$INSTALL_DIR"; then
        local rc_file
        rc_file=$(detect_shell_rc)

        if [[ -n "$rc_file" ]]; then
            echo "To add terragrunt to your PATH, run:"
            echo ""
            echo "  echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ${rc_file}"
            echo "  source ${rc_file}"
        fi
        [[ -z "$rc_file" ]] && {
            echo "Add to your shell configuration:"
            echo ""
            echo "  export PATH=\"${INSTALL_DIR}:\$PATH\""
        }
        echo ""
    fi

    echo "Run 'terragrunt --help' to get started."
    echo "For documentation, visit: https://docs.terragrunt.com/"
}

main "$@"
