#!/bin/sh
set -eu

# Install script for the src CLI (SourceCraft CLI).
# Usage:
#   curl -fsSL https://storage.yandexcloud.net/sourcecraft-cli/install.sh | sh
#
# Options:
#   -i [INSTALL_DIR]  Install to specified directory (default: ~/sourcecraft or /usr/local/bin)
#   -r [RC_FILE]      Automatically modify specified RC_FILE with PATH and completion
#   -n                Don't modify rc file and don't ask about it
#   -a                Automatically modify default rc file
#   -h                Print help
#
# Environment variables:
#   CHANNEL         - release channel: stable (default) or ptr
#   VERSION         - specific version to install (default: latest from channel)
#   INSTALL_DIR     - installation directory (can also use -i flag)
#   VERIFY_CHECKSUM - set to "0" to skip checksum verification (default: verify)
#   VERBOSE         - set to "1" to enable debug output

BASE_URL="${SRC_UPDATE_URL:-https://s3.yandexcloud.net/sourcecraft-cli}"
CLI_BIN="src"

CONTACT_SUPPORT_MESSAGE="If you think this should not happen, please report the issue.
System info: $(uname -a)"

# Enable verbose mode if requested
VERBOSE="${VERBOSE:-}"
if [ -n "$VERBOSE" ] && [ "$VERBOSE" != "0" ]; then
    set -x
fi

# Global variables for options
INSTALL_DIR=""
RC_PATH=""
NO_RC=""
AUTO_RC=""

# Shell-related globals
USER_SHELL=""
DEFAULT_RC_PATH=""
BASH_COMPLETION_AVAILABLE=""
ZSH_COMPLETION_AVAILABLE=""
FISH_COMPLETION_AVAILABLE=""
SH_SHELL=""

# Platform globals
OS=""
ARCH=""

# ============================================================================
# Curl with retry support
# ============================================================================

setup_curl() {
    CURL_HELP="${CLI_TEST_CURL_HELP:-$(curl --help 2>/dev/null || true)}"
    CURL_BASE_OPTIONS="-fsSL"
    CURL_RETRY_OPTIONS=""
    CURL_TIMEOUT_OPTIONS=""

    # Check for --retry support (added in curl 7.12.3)
    if echo "$CURL_HELP" | grep -q -- "--retry"; then
        CURL_RETRY_OPTIONS="--retry 5 --retry-delay 0 --retry-max-time 120"
    fi

    # Check for --connect-timeout support (added in curl 7.32.0)
    if echo "$CURL_HELP" | grep -q -- "--connect-timeout"; then
        CURL_TIMEOUT_OPTIONS="--connect-timeout 5 --max-time 300"
    fi

    # Check for --retry-connrefused support (added in curl 7.52.0)
    if echo "$CURL_HELP" | grep -q -- "--retry-connrefused"; then
        CURL_RETRY_OPTIONS="$CURL_RETRY_OPTIONS --retry-connrefused"
    fi
}

curl_with_retry() {
    # shellcheck disable=SC2086
    curl $CURL_BASE_OPTIONS $CURL_RETRY_OPTIONS $CURL_TIMEOUT_OPTIONS "$@"
}

# ============================================================================
# Parse command line options
# ============================================================================

parse_options() {
    while getopts "hi:r:na" opt; do
        case "$opt" in
            i)
                INSTALL_DIR="$OPTARG"
                ;;
            r)
                RC_PATH="$OPTARG"
                ;;
            n)
                NO_RC="yes"
                ;;
            a)
                AUTO_RC="yes"
                ;;
            h)
                print_usage
                exit 0
                ;;
            *)
                print_usage
                exit 1
                ;;
        esac
    done
}

print_usage() {
    cat <<EOF
Usage: install.sh [options...]
Options:
  -i [INSTALL_DIR]  Install to specified directory.
  -r [RC_FILE]      Automatically modify RC_FILE with PATH and shell completion.
  -n                Don't modify rc file and don't ask about it.
  -a                Automatically modify default rc file with PATH and completion.
  -h                Print this help message.

Environment variables:
  CHANNEL           Release channel: stable (default) or nightly
  VERSION           Specific version to install
  INSTALL_DIR       Installation directory
  VERIFY_CHECKSUM   Set to "0" to skip checksum verification
  VERBOSE           Set to "1" for debug output
EOF
}

# ============================================================================
# Platform detection
# ============================================================================

detect_os() {
    SYSTEM="${CLI_INSTALL_SYSTEM:-$(uname -s)}"
    case "$SYSTEM" in
        Linux|GNU/Linux)
            OS="linux"
            ;;
        Darwin)
            OS="darwin"
            ;;
        CYGWIN*|MINGW*|MSYS*|Windows_NT|WindowsNT)
            OS="windows"
            CLI_BIN="${CLI_BIN}.exe"
            ;;
        *)
            echo "Error: '$SYSTEM' operating system is not supported yet." >&2
            echo "$CONTACT_SUPPORT_MESSAGE" >&2
            exit 1
            ;;
    esac
}

detect_arch() {
    MACHINE="${CLI_INSTALL_MACHINE:-$(uname -m)}"
    case "$MACHINE" in
        x86_64|amd64|i686-64)
            ARCH="amd64"
            ;;
        i386|i686)
            ARCH="386"
            ;;
        arm64|aarch64|aarch64_be|armv8b|armv8l)
            if [ "$OS" = "windows" ]; then
                echo "Error: Windows arm machines are not supported yet." >&2
                echo "$CONTACT_SUPPORT_MESSAGE" >&2
                exit 1
            fi
            ARCH="arm64"
            ;;
        *)
            echo "Error: '$MACHINE' architecture is not supported yet." >&2
            echo "$CONTACT_SUPPORT_MESSAGE" >&2
            exit 1
            ;;
    esac
}

detect_shell() {
    USER_SHELL="$(basename "${SHELL:-sh}")"
    
    # Determine default RC path based on shell
    case "$USER_SHELL" in
        bash)
            if [ "$OS" = "darwin" ]; then
                DEFAULT_RC_PATH="${HOME}/.bash_profile"
            else
                DEFAULT_RC_PATH="${HOME}/.bashrc"
            fi
            ;;
        zsh)
            DEFAULT_RC_PATH="${HOME}/.zshrc"
            ;;
        fish)
            DEFAULT_RC_PATH="${HOME}/.config/fish/config.fish"
            ;;
        ash|sh)
            # ash and sh typically use .profile
            DEFAULT_RC_PATH="${HOME}/.profile"
            ;;
        *)
            DEFAULT_RC_PATH="${HOME}/.profile"
            ;;
    esac

    # Determine completion availability
    BASH_COMPLETION_AVAILABLE=""
    ZSH_COMPLETION_AVAILABLE=""
    FISH_COMPLETION_AVAILABLE=""
    SH_SHELL=""
    
    case "$USER_SHELL" in
        bash)
            BASH_COMPLETION_AVAILABLE="yes"
            ;;
        zsh)
            ZSH_COMPLETION_AVAILABLE="yes"
            ;;
        fish)
            FISH_COMPLETION_AVAILABLE="yes"
            ;;
        ash|sh)
            SH_SHELL="yes"
            ;;
    esac
}

# ============================================================================
# Version resolution
# ============================================================================

resolve_version() {
    CHANNEL="${CHANNEL:-stable}"
    
    # Validate channel
    case "$CHANNEL" in
        stable|nightly) ;;
        *)
            echo "Error: invalid channel: $CHANNEL (must be 'stable' or 'nightly')" >&2
            exit 1
            ;;
    esac

    if [ -n "${VERSION:-}" ]; then
        # Strip leading 'v' if present so we normalise to bare version.
        VERSION="${VERSION#v}"
        echo "Using specified version: ${VERSION}"
        return
    fi

    echo "Fetching latest version from ${CHANNEL} channel..."
    
    # Fetch plain text .version file (no JSON parsing needed)
    VERSION_URL="${BASE_URL}/channels/${CHANNEL}/.version"
    VERSION=$(curl_with_retry "$VERSION_URL" | tr -d '[:space:]') || {
        echo "Error: failed to fetch version from ${VERSION_URL}" >&2
        echo "Please specify VERSION manually: VERSION=x.y.z curl ... | sh" >&2
        exit 1
    }
    
    if [ -z "$VERSION" ] || ! echo "$VERSION" | grep -qE '^[0-9]+\.'; then
        echo "Error: could not determine latest version (got: '$VERSION')" >&2
        echo "Please specify VERSION manually: VERSION=x.y.z curl ... | sh" >&2
        exit 1
    fi
    
    echo "Latest version in ${CHANNEL} channel: ${VERSION}"
}

# ============================================================================
# Download and install
# ============================================================================

download_and_install() {
    # Set default install directory if not specified
    if [ -z "$INSTALL_DIR" ]; then
        INSTALL_DIR="${HOME}/sourcecraft"
    fi
    
    PLATFORM="${OS}_${ARCH}"

    # Structure: /releases/{version}/{platform}/src.tar.gz
    ARCHIVE_URL="${BASE_URL}/releases/${VERSION}/${PLATFORM}/src.tar.gz"
    CHECKSUM_URL="${BASE_URL}/releases/${VERSION}/${PLATFORM}/.checksum"

    WORK_DIR="$(mktemp -d)"
    trap 'rm -rf "$WORK_DIR"' EXIT INT TERM

    echo "Downloading ${CLI_BIN} v${VERSION} for ${OS}/${ARCH}..."
    curl_with_retry "$ARCHIVE_URL" -o "${WORK_DIR}/src.tar.gz" || {
        echo "Error: failed to download from ${ARCHIVE_URL}" >&2
        echo "Platform ${PLATFORM} may not be supported for this version." >&2
        exit 1
    }

    # Verify checksum if not disabled
    if [ "${VERIFY_CHECKSUM:-1}" != "0" ]; then
        echo "Fetching checksum..."
        EXPECTED_CHECKSUM=$(curl_with_retry "$CHECKSUM_URL" | tr -d '[:space:]') || {
            echo "Warning: could not fetch checksum, skipping verification" >&2
            EXPECTED_CHECKSUM=""
        }

        if [ -n "$EXPECTED_CHECKSUM" ]; then
            # Extract hash from "sha256:hash" format
            SHA256="${EXPECTED_CHECKSUM#sha256:}"
            
            echo "Verifying checksum..."
            if command -v sha256sum >/dev/null 2>&1; then
                ACTUAL_SHA256=$(sha256sum "${WORK_DIR}/src.tar.gz" | cut -d' ' -f1)
            elif command -v shasum >/dev/null 2>&1; then
                ACTUAL_SHA256=$(shasum -a 256 "${WORK_DIR}/src.tar.gz" | cut -d' ' -f1)
            else
                echo "Warning: no sha256sum or shasum available, skipping checksum verification" >&2
                ACTUAL_SHA256=""
            fi

            if [ -n "$ACTUAL_SHA256" ] && [ "$ACTUAL_SHA256" != "$SHA256" ]; then
                echo "Error: checksum mismatch!" >&2
                echo "  Expected: $SHA256" >&2
                echo "  Got:      $ACTUAL_SHA256" >&2
                exit 1
            fi
            
            if [ -n "$ACTUAL_SHA256" ]; then
                echo "Checksum verified."
            fi
        fi
    fi

    echo "Extracting..."
    tar -xzf "${WORK_DIR}/src.tar.gz" -C "$WORK_DIR"

    if [ ! -f "${WORK_DIR}/${CLI_BIN}" ]; then
        echo "Error: '${CLI_BIN}' binary not found in the downloaded archive" >&2
        exit 1
    fi

    chmod +x "${WORK_DIR}/${CLI_BIN}"
    
    # Verify binary works before moving
    "${WORK_DIR}/${CLI_BIN}" version || {
        echo "Error: downloaded binary does not appear to work." >&2
        echo "You may have downloaded the wrong architecture." >&2
        echo "$CONTACT_SUPPORT_MESSAGE" >&2
        exit 1
    }

    # Create install directory
    mkdir -p "${INSTALL_DIR}/bin"
    CLI_BIN_FULL_PATH="${INSTALL_DIR}/bin/${CLI_BIN}"
    mv -f "${WORK_DIR}/${CLI_BIN}" "${CLI_BIN_FULL_PATH}"
    
    # Create .install directory for metadata
    mkdir -p "${INSTALL_DIR}/.install"

    echo "Installed: ${CLI_BIN} to ${CLI_BIN_FULL_PATH}"
}

# ============================================================================
# Shell configuration (PATH and completion)
# ============================================================================

setup_shell_config() {
    # Generate PATH scripts for supported shells
    case "$USER_SHELL" in
        bash|zsh|fish|ash|sh)
            ;;
        *)
            echo "${CLI_BIN} is installed to ${CLI_BIN_FULL_PATH}"
            echo "Add ${INSTALL_DIR}/bin to your PATH manually."
            return
            ;;
    esac

    # Generate PATH script based on shell type
    case "$USER_SHELL" in
        bash)
            CLI_PATH_SCRIPT="${INSTALL_DIR}/path.bash.inc"
            cat >"${CLI_PATH_SCRIPT}" <<'EOF'
# Source this file to add src CLI to your PATH
cli_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
bin_path="${cli_dir}/bin"
export PATH="${bin_path}:${PATH}"
EOF
            ;;
        zsh)
            CLI_PATH_SCRIPT="${INSTALL_DIR}/path.zsh.inc"
            cat >"${CLI_PATH_SCRIPT}" <<'EOF'
# Source this file to add src CLI to your PATH
cli_dir="$(cd "$(dirname "${(%):-%N}")" && pwd)"
bin_path="${cli_dir}/bin"
export PATH="${bin_path}:${PATH}"
EOF
            ;;
        fish)
            CLI_PATH_SCRIPT="${INSTALL_DIR}/path.fish"
            cat >"${CLI_PATH_SCRIPT}" <<EOF
# Source this file to add src CLI to your PATH
set -gx PATH "${INSTALL_DIR}/bin" \$PATH
EOF
            ;;
        ash|sh)
            CLI_PATH_SCRIPT="${INSTALL_DIR}/path.sh.inc"
            cat >"${CLI_PATH_SCRIPT}" <<EOF
# Source this file to add src CLI to your PATH
export PATH="${INSTALL_DIR}/bin:\${PATH}"
EOF
            ;;
    esac

    # Generate bash completion script
    CLI_BASH_COMPLETION="${INSTALL_DIR}/completion.bash.inc"
    if [ "$BASH_COMPLETION_AVAILABLE" = "yes" ]; then
        "${CLI_BIN_FULL_PATH}" completion bash > "${CLI_BASH_COMPLETION}" 2>/dev/null || true
    fi

    # Generate zsh completion script
    CLI_ZSH_COMPLETION="${INSTALL_DIR}/completion.zsh.inc"
    if [ "$ZSH_COMPLETION_AVAILABLE" = "yes" ]; then
        "${CLI_BIN_FULL_PATH}" completion zsh > "${CLI_ZSH_COMPLETION}" 2>/dev/null || true
    fi

    # Generate fish completion script
    CLI_FISH_COMPLETION="${INSTALL_DIR}/completion.fish"
    if [ "$FISH_COMPLETION_AVAILABLE" = "yes" ]; then
        "${CLI_BIN_FULL_PATH}" completion fish > "${CLI_FISH_COMPLETION}" 2>/dev/null || true
    fi
}

# ============================================================================
# RC file modification
# ============================================================================

modify_rc() {
    rc_file="$1"
    
    # Handle fish shell separately (different syntax)
    if [ "$USER_SHELL" = "fish" ]; then
        modify_rc_fish "$rc_file"
        return
    fi
    
    # Handle POSIX-compatible shells (bash, zsh, ash, sh)
    # Add PATH modification if not already present
    if ! grep -Fq "if [ -f '${CLI_PATH_SCRIPT}' ]; then source '${CLI_PATH_SCRIPT}'; fi" "$rc_file" 2>/dev/null && \
       ! grep -Fq "if [ -f '${CLI_PATH_SCRIPT}' ]; then . '${CLI_PATH_SCRIPT}'; fi" "$rc_file" 2>/dev/null; then
        if [ "$SH_SHELL" = "yes" ]; then
            # Use POSIX-compatible dot notation for ash/sh
            cat >> "$rc_file" <<EOF

# The next line updates PATH for src CLI.
if [ -f '${CLI_PATH_SCRIPT}' ]; then . '${CLI_PATH_SCRIPT}'; fi
EOF
        else
            cat >> "$rc_file" <<EOF

# The next line updates PATH for src CLI.
if [ -f '${CLI_PATH_SCRIPT}' ]; then source '${CLI_PATH_SCRIPT}'; fi
EOF
        fi
        echo ""
        echo "${CLI_BIN} PATH has been added to your '${rc_file}' profile"
    fi

    # Add completion if available and not already present
    if [ "$BASH_COMPLETION_AVAILABLE" = "yes" ]; then
        if ! grep -Fq "if [ -f '${CLI_BASH_COMPLETION}' ]; then source '${CLI_BASH_COMPLETION}'; fi" "$rc_file" 2>/dev/null; then
            cat >> "$rc_file" <<EOF

# The next line enables shell command completion for src.
if [ -f '${CLI_BASH_COMPLETION}' ]; then source '${CLI_BASH_COMPLETION}'; fi
EOF
            echo "${CLI_BIN} bash completion has been added to your '${rc_file}' profile."
            if [ "$OS" = "darwin" ]; then
                echo "Note: Make sure bash-completion is installed (brew install bash-completion)"
            fi
        fi
    elif [ "$ZSH_COMPLETION_AVAILABLE" = "yes" ]; then
        if ! grep -Fq "if [ -f '${CLI_ZSH_COMPLETION}' ]; then source '${CLI_ZSH_COMPLETION}'; fi" "$rc_file" 2>/dev/null; then
            cat >> "$rc_file" <<EOF

# The next line enables shell command completion for src.
if [ -f '${CLI_ZSH_COMPLETION}' ]; then source '${CLI_ZSH_COMPLETION}'; fi
EOF
            echo "${CLI_BIN} zsh completion has been added to your '${rc_file}' profile."
        fi
    fi
    # Note: ash/sh typically don't support programmable completion

    echo ""
    if [ "$SH_SHELL" = "yes" ]; then
        echo "To complete installation, start a new shell or type '. \"$rc_file\"' in the current one"
    else
        echo "To complete installation, start a new shell (exec -l \$SHELL) or type 'source \"$rc_file\"' in the current one"
    fi
}

modify_rc_fish() {
    rc_file="$1"
    
    # Ensure config directory exists for fish
    rc_dir="$(dirname "$rc_file")"
    if [ ! -d "$rc_dir" ]; then
        mkdir -p "$rc_dir"
    fi
    
    # Add PATH modification if not already present
    if ! grep -Fq "set -gx PATH \"${INSTALL_DIR}/bin\"" "$rc_file" 2>/dev/null; then
        cat >> "$rc_file" <<EOF

# The next line updates PATH for src CLI.
if test -f '${CLI_PATH_SCRIPT}'
    source '${CLI_PATH_SCRIPT}'
end
EOF
        echo ""
        echo "${CLI_BIN} PATH has been added to your '${rc_file}' profile"
    fi

    # Add fish completion if available and not already present
    if [ "$FISH_COMPLETION_AVAILABLE" = "yes" ]; then
        if ! grep -Fq "${CLI_FISH_COMPLETION}" "$rc_file" 2>/dev/null; then
            cat >> "$rc_file" <<EOF

# The next line enables shell command completion for src.
if test -f '${CLI_FISH_COMPLETION}'
    source '${CLI_FISH_COMPLETION}'
end
EOF
            echo "${CLI_BIN} fish completion has been added to your '${rc_file}' profile."
        fi
    fi

    echo ""
    echo "To complete installation, start a new shell or type 'source \"$rc_file\"' in the current one"
}

input_yes_no() {
    while true; do
        read -r answer || return 1
        case "$answer" in
            Yes|y|yes|Y|"")
                return 0
                ;;
            No|n|no|N)
                return 1
                ;;
            *)
                printf "Please enter 'y' or 'n': "
                ;;
        esac
    done
}

ask_for_rc_path() {
    echo "Enter a path to an rc file to update, or leave blank to use"
    printf "[%s]: " "$DEFAULT_RC_PATH"
    read -r filepath || filepath=""
    if [ -z "$filepath" ]; then
        filepath="$DEFAULT_RC_PATH"
    fi
    RC_PATH="$filepath"
}

print_rc_guide() {
    echo ""
    echo "To add ${CLI_BIN} to your PATH, add the following to your shell profile:"
    
    case "$USER_SHELL" in
        fish)
            echo "  source '${CLI_PATH_SCRIPT}'"
            ;;
        ash|sh)
            echo "  . '${CLI_PATH_SCRIPT}'"
            ;;
        *)
            echo "  source '${CLI_PATH_SCRIPT}'"
            ;;
    esac
    
    echo ""
    if [ "$BASH_COMPLETION_AVAILABLE" = "yes" ]; then
        echo "To enable bash completion, add:"
        echo "  source '${CLI_BASH_COMPLETION}'"
    elif [ "$ZSH_COMPLETION_AVAILABLE" = "yes" ]; then
        echo "To enable zsh completion, add:"
        echo "  source '${CLI_ZSH_COMPLETION}'"
    elif [ "$FISH_COMPLETION_AVAILABLE" = "yes" ]; then
        echo "To enable fish completion, add:"
        echo "  source '${CLI_FISH_COMPLETION}'"
    elif [ "$SH_SHELL" = "yes" ]; then
        echo "Note: ash/sh shells typically don't support programmable completion."
    fi
}

handle_rc_modification() {
    # Skip for unsupported shells
    case "$USER_SHELL" in
        bash|zsh|fish|ash|sh)
            ;;
        *)
            return
            ;;
    esac

    # If -n flag was used, skip RC modification
    if [ "$NO_RC" = "yes" ]; then
        print_rc_guide
        return
    fi

    # If explicit RC path was provided via -r flag
    if [ -n "$RC_PATH" ]; then
        modify_rc "$RC_PATH"
        return
    fi

    # If -a flag was used or stdin is not a terminal (piped)
    if [ "$AUTO_RC" = "yes" ]; then
        modify_rc "$DEFAULT_RC_PATH"
        return
    fi

    # Interactive mode - ask user
    printf "Modify profile to update your \$PATH and enable shell command completion? [Y/n] "
    if input_yes_no; then
        ask_for_rc_path
        modify_rc "$RC_PATH"
    else
        print_rc_guide
    fi
}

# ============================================================================
# Success message
# ============================================================================

print_success() {
    cat <<EOF

${CLI_BIN} has been installed to ${CLI_BIN_FULL_PATH}

Next steps:
  src               - launch onboarding
  src pr list       - list pull requests
  src --help        - see all commands

EOF
}

# ============================================================================
# Main
# ============================================================================

main() {
    # Detect if stdin is not a terminal (piped) - auto-enable non-interactive mode
    if [ ! -t 0 ]; then
        AUTO_RC="yes"
    fi

    parse_options "$@"
    setup_curl
    detect_os
    detect_arch
    detect_shell
    resolve_version
    download_and_install
    setup_shell_config
    print_success
    handle_rc_modification
}

main "$@"
