#!/bin/sh
set -eu

NETSODY_INSTALL_BASE_URL="${NETSODY_INSTALL_BASE_URL:-https://download.netsody.io/binaries}"
NETSODY_INSTALL_ARTIFACTS_BASE_URL="${NETSODY_INSTALL_ARTIFACTS_BASE_URL:-https://artifacts.netsody.io/runs}"
NETSODY_INSTALL_PLATFORM="${NETSODY_INSTALL_PLATFORM:-}"
NETSODY_INSTALL_ASSUME_YES=0
NETSODY_INSTALL_LIBRARY_MODE="${NETSODY_INSTALL_LIBRARY_MODE:-0}"
NETSODY_INSTALL_CHANNEL="stable"

say() {
    printf '%s\n' "$*"
}

warn() {
    printf 'warning: %s\n' "$*" >&2
}

die() {
    printf 'error: %s\n' "$*" >&2
    exit 1
}

need_cmd() {
    command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

have_cmd() {
    command -v "$1" >/dev/null 2>&1
}

trim() {
    printf '%s' "$1" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
}

normalize_os() {
    raw_os=$(uname -s)
    case "$raw_os" in
        Linux)
            printf '%s\n' "linux"
            ;;
        Darwin)
            printf '%s\n' "macos"
            ;;
        CYGWIN*|MINGW*|MSYS*)
            printf '%s\n' "windows"
            ;;
        *)
            die "unsupported operating system: $raw_os"
            ;;
    esac
}

normalize_arch() {
    raw_arch=$(uname -m)
    case "$raw_arch" in
        x86_64|amd64)
            printf '%s\n' "amd64"
            ;;
        aarch64|arm64)
            printf '%s\n' "arm64"
            ;;
        armv7l|armv7|armhf)
            printf '%s\n' "armv7"
            ;;
        *)
            die "unsupported architecture: $raw_arch"
            ;;
    esac
}

os_release_file() {
    printf '%s\n' "${NETSODY_INSTALL_OS_RELEASE:-/etc/os-release}"
}

read_os_release_field() {
    field=$1
    file=$(os_release_file)
    [ -r "$file" ] || return 1

    (
        # shellcheck disable=SC1090
        . "$file"
        eval "value=\${$field-}"
        printf '%s\n' "$value"
    )
}

matches_word() {
    haystack=${1:-}
    needle=$2
    case " $haystack " in
        *" $needle "*) return 0 ;;
        *) return 1 ;;
    esac
}

is_debian_like_linux() {
    distro_id=${1:-}
    distro_like=${2:-}

    case "$distro_id" in
        debian|ubuntu|raspbian|linuxmint|pop|kali|neon|elementary|zorin|devuan)
            return 0
            ;;
    esac

    matches_word "$distro_like" "debian" || matches_word "$distro_like" "ubuntu"
}

is_arch_like_linux() {
    distro_id=${1:-}
    distro_like=${2:-}

    case "$distro_id" in
        arch|archlinux|manjaro|endeavouros)
            return 0
            ;;
    esac

    matches_word "$distro_like" "arch" || matches_word "$distro_like" "archlinux"
}

is_fedora_like_linux() {
    distro_id=${1:-}
    distro_like=${2:-}

    case "$distro_id" in
        fedora)
            return 0
            ;;
    esac

    matches_word "$distro_like" "fedora"
}

validate_platform_override() {
    platform=$1
    case "$platform" in
        windows-amd64|linux-amd64-latest|macos-amd64|macos-arm64|linux-amd64|linux-arm64|linux-amd64-fedora|linux-armv7)
            ;;
        *)
            die "unsupported platform override: $platform"
            ;;
    esac
}

validate_platform_override_compatibility() {
    detected_os=$1
    detected_arch=$2
    selected_platform=$3

    case "$detected_os:$detected_arch" in
        linux:amd64)
            case "$selected_platform" in
                linux-amd64|linux-amd64-fedora|linux-amd64-latest) return 0 ;;
            esac
            ;;
        linux:arm64)
            [ "$selected_platform" = "linux-arm64" ] && return 0
            ;;
        linux:armv7)
            [ "$selected_platform" = "linux-armv7" ] && return 0
            ;;
        macos:arm64)
            [ "$selected_platform" = "macos-arm64" ] && return 0
            ;;
        macos:amd64)
            [ "$selected_platform" = "macos-amd64" ] && return 0
            ;;
        windows:amd64)
            [ "$selected_platform" = "windows-amd64" ] && return 0
            ;;
    esac

    die "platform override $selected_platform is incompatible with the detected system ${detected_os}/${detected_arch}"
}

detect_platform() {
    detected_os=$1
    detected_arch=$2

    case "$detected_os" in
        macos)
            case "$detected_arch" in
                arm64)
                    printf '%s\n' "macos-arm64"
                    return 0
                    ;;
                amd64)
                    printf '%s\n' "macos-amd64"
                    return 0
                    ;;
                *)
                    die "unsupported macOS architecture for this installer rollout: $detected_arch"
                    ;;
            esac
            ;;
        windows)
            case "$detected_arch" in
                amd64)
                    printf '%s\n' "windows-amd64"
                    return 0
                    ;;
                *)
                    die "unsupported Windows architecture for this installer rollout: $detected_arch"
                    ;;
            esac
            ;;
        linux)
            distro_id=$(trim "$(read_os_release_field ID || true)")
            distro_like=$(trim "$(read_os_release_field ID_LIKE || true)")

            [ -n "$distro_id" ] || die "unable to determine Linux distribution from $(os_release_file)"

            case "$detected_arch" in
                amd64)
                    if is_arch_like_linux "$distro_id" "$distro_like"; then
                        printf '%s\n' "linux-amd64-latest"
                    elif is_fedora_like_linux "$distro_id" "$distro_like"; then
                        printf '%s\n' "linux-amd64-fedora"
                    elif is_debian_like_linux "$distro_id" "$distro_like"; then
                        printf '%s\n' "linux-amd64"
                    else
                        die "unsupported Linux distribution for amd64: ${distro_id}${distro_like:+ (id_like: $distro_like)}"
                    fi
                    ;;
                arm64)
                    if is_debian_like_linux "$distro_id" "$distro_like"; then
                        printf '%s\n' "linux-arm64"
                    else
                        die "unsupported Linux distribution for arm64: ${distro_id}${distro_like:+ (id_like: $distro_like)}"
                    fi
                    ;;
                armv7)
                    if is_debian_like_linux "$distro_id" "$distro_like"; then
                        printf '%s\n' "linux-armv7"
                    else
                        die "unsupported Linux distribution for armv7: ${distro_id}${distro_like:+ (id_like: $distro_like)}"
                    fi
                    ;;
                *)
                    die "unsupported Linux architecture: $detected_arch"
                    ;;
            esac
            ;;
        *)
            die "unsupported operating system: $detected_os"
            ;;
    esac
}

rpm_artifact_name() {
    version=$1
    case "$version" in
        *-*)
            rpm_version=${version%%-*}
            rpm_release="0.${version#*-}"
            ;;
        *)
            rpm_version=$version
            rpm_release="1"
            ;;
    esac

    printf 'netsody-%s-%s.x86_64.rpm\n' "$rpm_version" "$rpm_release"
}

arch_artifact_name() {
    version=$1
    case "$version" in
        *-*)
            arch_version_main=${version%%-*}
            arch_version_pre=${version#*-}
            arch_pkgver="${arch_version_main}_$(printf '%s' "$arch_version_pre" | tr -d '.')"
            ;;
        *)
            arch_pkgver=$version
            ;;
    esac

    printf 'netsody-agent_%s-1_x86_64.pkg.tar.zst\n' "$arch_pkgver"
}

deb_artifact_version() {
    version=$1
    case "$version" in
        *-*)
            printf '%s~%s\n' "${version%%-*}" "${version#*-}"
            ;;
        *)
            printf '%s\n' "$version"
            ;;
    esac
}

artifact_name_for_platform() {
    platform=$1
    version=$2

    case "$platform" in
        macos-arm64)
            printf 'Netsody_%s_macos_arm64.pkg\n' "$version"
            ;;
        macos-amd64)
            printf 'Netsody_%s_macos_x86_64.pkg\n' "$version"
            ;;
        windows-amd64)
            printf 'Netsody_%s_windows.exe\n' "$version"
            ;;
        linux-amd64)
            printf 'netsody_%s_amd64.deb\n' "$(deb_artifact_version "$version")"
            ;;
        linux-arm64)
            printf 'netsody_%s_arm64.deb\n' "$(deb_artifact_version "$version")"
            ;;
        linux-armv7)
            printf 'netsody_%s_armv7.deb\n' "$(deb_artifact_version "$version")"
            ;;
        linux-amd64-fedora)
            rpm_artifact_name "$version"
            ;;
        linux-amd64-latest)
            arch_artifact_name "$version"
            ;;
        *)
            die "unsupported platform identifier: $platform"
            ;;
    esac
}

version_url_for_platform() {
    platform=$1
    printf '%s/%s/latest/version\n' "$NETSODY_INSTALL_BASE_URL" "$platform"
}

artifact_url_for_platform() {
    platform=$1
    version=$2
    artifact_name=$(artifact_name_for_platform "$platform" "$version")
    printf '%s/%s/%s/%s\n' "$NETSODY_INSTALL_BASE_URL" "$platform" "$version" "$artifact_name"
}

nightly_runs_url() {
    printf '%s/\n' "$NETSODY_INSTALL_ARTIFACTS_BASE_URL"
}

nightly_platform_url() {
    run_id=$1
    platform=$2
    printf '%s/%s/%s/\n' "$NETSODY_INSTALL_ARTIFACTS_BASE_URL" "$run_id" "$platform"
}

install_method_for_platform() {
    platform=$1
    case "$platform" in
        macos-arm64|macos-amd64)
            printf '%s\n' "macOS pkg via installer"
            ;;
        windows-amd64)
            printf '%s\n' "Windows .exe installer"
            ;;
        linux-amd64|linux-arm64|linux-armv7)
            printf '%s\n' "Debian package"
            ;;
        linux-amd64-fedora)
            printf '%s\n' "RPM package"
            ;;
        linux-amd64-latest)
            printf '%s\n' "Arch package"
            ;;
        *)
            die "unsupported platform identifier: $platform"
            ;;
    esac
}

extract_listing_hrefs() {
    printf '%s\n' "$1" | sed -n 's/.*href="\([^"]*\)".*/\1/p'
}

extract_nightly_run_ids() {
    printf '%s\n' "$1" | sed -n 's/.*href="\([0-9][0-9]*\)\/".*/\1/p'
}

nightly_artifact_pattern_for_platform() {
    platform=$1
    case "$platform" in
        linux-amd64|linux-arm64|linux-armv7)
            printf '%s\n' "netsody_*.deb"
            ;;
        linux-amd64-fedora)
            printf '%s\n' "netsody-*.rpm"
            ;;
        linux-amd64-latest)
            printf '%s\n' "netsody-agent_*.pkg.tar.zst"
            ;;
        macos-arm64|macos-amd64)
            printf '%s\n' "Netsody_*.pkg"
            ;;
        windows-amd64)
            printf '%s\n' "Netsody_*.exe"
            ;;
        *)
            die "unsupported platform identifier: $platform"
            ;;
    esac
}

find_nightly_artifact_name() {
    platform=$1
    platform_listing=$2
    pattern=$(nightly_artifact_pattern_for_platform "$platform")
    matches=""

    for href in $(extract_listing_hrefs "$platform_listing"); do
        case "$href" in
            $pattern)
                if [ -n "$matches" ]; then
                    die "multiple nightly artifacts matched for $platform: $matches $href"
                fi
                matches=$href
                ;;
        esac
    done

    [ -n "$matches" ] || return 1
    printf '%s\n' "$matches"
}

select_nightly_artifact_name() {
    platform=$1
    platform_listing=$2

    find_nightly_artifact_name "$platform" "$platform_listing" || die "no nightly artifact matched for $platform"
}

select_latest_nightly_artifact() {
    platform=$1
    runs_listing=$(fetch_text "$(nightly_runs_url)")
    run_ids=$(extract_nightly_run_ids "$runs_listing")

    [ -n "$run_ids" ] || die "no nightly run directories found at $(nightly_runs_url)"

    for run_id in $(printf '%s\n' "$run_ids" | sort -nr); do
        platform_url=$(nightly_platform_url "$run_id" "$platform")

        if ! platform_listing=$(try_fetch_text "$platform_url"); then
            warn "skipping nightly run $run_id for $platform: artifact listing is not available yet"
            continue
        fi

        if artifact_name=$(find_nightly_artifact_name "$platform" "$platform_listing"); then
            printf '%s\n%s\n' "$run_id" "$artifact_name"
            return 0
        fi

        warn "skipping nightly run $run_id for $platform: artifact is not available yet"
    done

    die "no complete nightly artifact found for $platform at $(nightly_runs_url)"
}

display_version_from_artifact_name() {
    platform=$1
    artifact_name=$2

    case "$platform" in
        linux-amd64)
            version=${artifact_name#netsody_}
            version=${version%_amd64.deb}
            ;;
        linux-arm64)
            version=${artifact_name#netsody_}
            version=${version%_arm64.deb}
            ;;
        linux-armv7)
            version=${artifact_name#netsody_}
            version=${version%_armv7.deb}
            ;;
        linux-amd64-fedora)
            version=${artifact_name#netsody-}
            version=${version%.x86_64.rpm}
            ;;
        linux-amd64-latest)
            version=${artifact_name#netsody-agent_}
            version=${version%_x86_64.pkg.tar.zst}
            ;;
        macos-arm64)
            version=${artifact_name#Netsody_}
            version=${version%_macos_arm64.pkg}
            ;;
        macos-amd64)
            version=${artifact_name#Netsody_}
            version=${version%_macos_x86_64.pkg}
            ;;
        windows-amd64)
            version=${artifact_name#Netsody_}
            version=${version%_windows.exe}
            ;;
        *)
            die "unsupported platform identifier: $platform"
            ;;
    esac

    printf '%s\n' "$version"
}

fetch_text() {
    url=$1
    if have_cmd curl; then
        curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location "$url"
        return 0
    fi

    if have_cmd wget; then
        wget --https-only -qO- "$url"
        return 0
    fi

    die "neither curl nor wget is available"
}

try_fetch_text() {
    url=$1
    if have_cmd curl; then
        curl --proto '=https' --tlsv1.2 --fail --silent --location "$url"
        return $?
    fi

    if have_cmd wget; then
        wget --https-only -qO- "$url"
        return $?
    fi

    die "neither curl nor wget is available"
}

download_to_file() {
    url=$1
    path=$2

    if have_cmd curl; then
        curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location --output "$path" "$url"
        return 0
    fi

    if have_cmd wget; then
        wget --https-only -qO "$path" "$url"
        return 0
    fi

    die "neither curl nor wget is available"
}

prompt_input() {
    prompt=$1

    if [ -t 0 ]; then
        printf '%s' "$prompt" >&2
        IFS= read -r response || die "failed to read confirmation"
        printf '%s\n' "$response"
        return 0
    fi

    if [ -r /dev/tty ]; then
        printf '%s' "$prompt" > /dev/tty
        IFS= read -r response < /dev/tty || die "failed to read confirmation"
        printf '%s\n' "$response"
        return 0
    fi

    die "no interactive terminal available for confirmation; re-run with -y after reviewing the detected values"
}

confirm_or_abort() {
    if [ "$NETSODY_INSTALL_ASSUME_YES" -eq 1 ]; then
        return 0
    fi

    response=$(prompt_input "Proceed with the installation? [y/N] ")
    case "$response" in
        y|Y|yes|YES)
            return 0
            ;;
        *)
            die "installation aborted by user"
            ;;
    esac
}

resolve_privilege_tool() {
    if [ "$(id -u)" -eq 0 ]; then
        printf '%s\n' ""
        return 0
    fi

    if have_cmd sudo; then
        printf '%s\n' "sudo"
        return 0
    fi

    if have_cmd doas; then
        printf '%s\n' "doas"
        return 0
    fi

    die "root privileges are required, but neither sudo nor doas is available"
}

run_with_privilege() {
    if [ -n "${NETSODY_INSTALL_PRIVILEGE_TOOL:-}" ]; then
        "$NETSODY_INSTALL_PRIVILEGE_TOOL" "$@"
    else
        "$@"
    fi
}

check_macos_homebrew_conflict() {
    case "${NETSODY_SELECTED_PLATFORM:-}" in
        macos-arm64|macos-amd64)
            ;;
        *)
            return 0
            ;;
    esac

    if ! have_cmd brew; then
        return 0
    fi

    if brew list --formula 2>/dev/null | grep -qx "netsody"; then
        die "Netsody appears to be installed via Homebrew; use Homebrew to manage that installation"
    fi
}

install_downloaded_artifact() {
    platform=$1
    path=$2

    case "$platform" in
        macos-arm64|macos-amd64)
            need_cmd installer
            NETSODY_INSTALL_PRIVILEGE_TOOL=$(resolve_privilege_tool)
            run_with_privilege installer -pkg "$path" -target /
            ;;
        linux-amd64|linux-arm64|linux-armv7)
            NETSODY_INSTALL_PRIVILEGE_TOOL=$(resolve_privilege_tool)
            if have_cmd dpkg; then
                run_with_privilege dpkg -i "$path"
            elif have_cmd apt-get; then
                run_with_privilege apt-get install --reinstall -y "$path"
            elif have_cmd apt; then
                run_with_privilege apt install --reinstall -y "$path"
            else
                die "no supported Debian package installer found (expected apt-get, apt, or dpkg)"
            fi
            ;;
        linux-amd64-fedora)
            NETSODY_INSTALL_PRIVILEGE_TOOL=$(resolve_privilege_tool)
            if have_cmd dnf; then
                run_with_privilege dnf install -y "$path"
            elif have_cmd yum; then
                run_with_privilege yum install -y "$path"
            elif have_cmd rpm; then
                run_with_privilege rpm -Uvh "$path"
            else
                die "no supported RPM package installer found (expected dnf, yum, or rpm)"
            fi
            ;;
        linux-amd64-latest)
            NETSODY_INSTALL_PRIVILEGE_TOOL=$(resolve_privilege_tool)
            if have_cmd pacman; then
                run_with_privilege pacman -U --needed --noconfirm "$path"
            else
                die "no supported Arch package installer found (expected pacman)"
            fi
            ;;
        windows-amd64)
            if have_cmd cmd.exe && have_cmd cygpath; then
                windows_path=$(cygpath -w "$path")
                cmd.exe /C start "" /wait "$windows_path"
            else
                chmod +x "$path" >/dev/null 2>&1 || true
                "$path"
            fi
            ;;
        *)
            die "unsupported platform identifier: $platform"
            ;;
    esac
}

print_summary() {
    channel=$1
    selected_platform=$2
    detected_platform=$3
    version=$4
    version_url=$5
    artifact_name=$6
    artifact_url=$7
    install_method=$8
    detected_os=$9
    detected_arch=${10}
    nightly_run_id=${11:-}

    distro_pretty=$(trim "$(read_os_release_field PRETTY_NAME || true)")
    distro_id=$(trim "$(read_os_release_field ID || true)")
    distro_like=$(trim "$(read_os_release_field ID_LIKE || true)")

    say "Netsody installer"
    say ""
    say "Detected system:"
    say "  OS:               $detected_os"
    say "  Architecture:     $detected_arch"

    if [ "$detected_os" = "linux" ]; then
        say "  Distribution:     ${distro_pretty:-unknown}"
        say "  Distribution ID:  ${distro_id:-unknown}"
        say "  ID_LIKE:          ${distro_like:-unknown}"
    fi

    say "  Detected target:  $detected_platform"

    if [ -n "$NETSODY_INSTALL_PLATFORM" ]; then
        say ""
        say "Override:"
        say "  Selected target:  $selected_platform"
    fi

    say ""
    say "Planned installation:"
    say "  Channel:          $channel"
    say "  Version source:   $version_url"
    say "  Version:          $version"
    if [ -n "$nightly_run_id" ]; then
        say "  Nightly run:      $nightly_run_id"
    fi
    say "  Artifact name:    $artifact_name"
    say "  Artifact URL:     $artifact_url"
    say "  Install method:   $install_method"
    say ""
    say "Supported platform identifiers:"
    say "  windows-amd64"
    say "  linux-amd64-latest"
    say "  macos-amd64"
    say "  macos-arm64"
    say "  linux-amd64"
    say "  linux-arm64"
    say "  linux-amd64-fedora"
    say "  linux-armv7"
    say ""
    say "If this detection is wrong, abort now and re-run with:"
    say "  curl ... | sh -s -- --platform <identifier>"
    say ""
}

usage() {
    cat <<'EOF'
Usage: install.sh [OPTIONS]

Options:
      --nightly            Install the newest nightly artifact from artifacts.netsody.io/runs
  -y, --yes                Skip the confirmation prompt
      --platform IDENT     Override the detected platform identifier
  -h, --help               Show this help text
EOF
}

parse_args() {
    while [ "$#" -gt 0 ]; do
        case "$1" in
            -y|--yes)
                NETSODY_INSTALL_ASSUME_YES=1
                ;;
            --nightly)
                NETSODY_INSTALL_CHANNEL="nightly"
                ;;
            --platform)
                [ "$#" -ge 2 ] || die "--platform requires an argument"
                NETSODY_INSTALL_PLATFORM=$2
                shift
                ;;
            -h|--help)
                usage
                exit 0
                ;;
            --)
                shift
                break
                ;;
            *)
                die "unknown argument: $1"
                ;;
        esac
        shift
    done
}

main() {
    parse_args "$@"

    need_cmd uname
    need_cmd mktemp
    need_cmd rm
    need_cmd sed

    if [ -n "$NETSODY_INSTALL_PLATFORM" ]; then
        validate_platform_override "$NETSODY_INSTALL_PLATFORM"
    fi

    detected_os=$(normalize_os)
    detected_arch=$(normalize_arch)
    detected_platform=$(detect_platform "$detected_os" "$detected_arch")
    selected_platform=$detected_platform

    if [ -n "$NETSODY_INSTALL_PLATFORM" ]; then
        selected_platform=$NETSODY_INSTALL_PLATFORM
        validate_platform_override_compatibility "$detected_os" "$detected_arch" "$selected_platform"
    fi

    NETSODY_SELECTED_PLATFORM=$selected_platform
    check_macos_homebrew_conflict

    nightly_run_id=""
    install_method=$(install_method_for_platform "$selected_platform")

    if [ "$NETSODY_INSTALL_CHANNEL" = "nightly" ]; then
        need_cmd sort
        nightly_selection=$(select_latest_nightly_artifact "$selected_platform")
        nightly_run_id=$(printf '%s\n' "$nightly_selection" | sed -n '1p')
        artifact_name=$(printf '%s\n' "$nightly_selection" | sed -n '2p')
        version_url=$(nightly_platform_url "$nightly_run_id" "$selected_platform")
        version=$(display_version_from_artifact_name "$selected_platform" "$artifact_name")
        artifact_url="${version_url}${artifact_name}"
    else
        version_url=$(version_url_for_platform "$selected_platform")
        version=$(fetch_text "$version_url")
        version=$(trim "$version")
        [ -n "$version" ] || die "version endpoint returned an empty response: $version_url"
        artifact_name=$(artifact_name_for_platform "$selected_platform" "$version")
        artifact_url=$(artifact_url_for_platform "$selected_platform" "$version")
    fi

    print_summary \
        "$NETSODY_INSTALL_CHANNEL" \
        "$selected_platform" \
        "$detected_platform" \
        "$version" \
        "$version_url" \
        "$artifact_name" \
        "$artifact_url" \
        "$install_method" \
        "$detected_os" \
        "$detected_arch" \
        "$nightly_run_id"

    confirm_or_abort

    tmp_dir=$(mktemp -d)
    cleanup() {
        rm -rf "$tmp_dir"
    }
    trap cleanup EXIT INT TERM HUP

    artifact_path="$tmp_dir/$artifact_name"

    say "Downloading $artifact_name ..."
    download_to_file "$artifact_url" "$artifact_path"
    say "Download completed."
    say ""
    say "Installing Netsody ..."
    install_downloaded_artifact "$selected_platform" "$artifact_path"
    say ""
    say "Netsody installation completed."
}

if [ "$NETSODY_INSTALL_LIBRARY_MODE" -ne 1 ]; then
    main "$@"
fi
