#!/bin/sh
# InariWatch Agent Installer
# Usage: curl -sf https://install.inariwatch.com | sh
#    or: curl -sf https://install.inariwatch.com | sh -s -- --dsn <DSN>
#
# Supports: Ubuntu 22.04+, Debian 12+, Amazon Linux 2023+, Fedora 38+, RHEL 9+

set -eu

# --------------------------------------------------------------------------- #
#  Constants
# --------------------------------------------------------------------------- #

INSTALL_DIR="/usr/local/bin"
BINARY_NAME="inariwatch-agent"
CONFIG_DIR="/etc/inariwatch"
DATA_DIR="/var/lib/inariwatch/buffer"
RUN_DIR="/var/run/inariwatch"
SERVICE_FILE="/etc/systemd/system/inariwatch-agent.service"
SERVICE_USER="inariwatch"
RELEASES_BASE="https://github.com/orbita-pos/inariwatch-agent-releases/releases/download"

DEFAULT_VERSION="0.2.0"
MIN_KERNEL_MAJOR=5
MIN_KERNEL_MINOR=8

# --------------------------------------------------------------------------- #
#  Color helpers
# --------------------------------------------------------------------------- #

if [ -t 1 ] && command -v tput >/dev/null 2>&1; then
    GREEN=$(tput setaf 2)
    RED=$(tput setaf 1)
    YELLOW=$(tput setaf 3)
    BOLD=$(tput bold)
    RESET=$(tput sgr0)
else
    GREEN=""
    RED=""
    YELLOW=""
    BOLD=""
    RESET=""
fi

info()  { printf '%s[+]%s %s\n' "$GREEN"  "$RESET" "$1"; }
warn()  { printf '%s[!]%s %s\n' "$YELLOW" "$RESET" "$1"; }
err()   { printf '%s[x]%s %s\n' "$RED"    "$RESET" "$1" >&2; }
fatal() { err "$1"; exit 1; }

step() {
    printf '\n%s>>> %s%s\n' "$BOLD" "$1" "$RESET"
}

ok() {
    printf '%s  ✓ %s%s\n' "$GREEN" "$1" "$RESET"
}

# --------------------------------------------------------------------------- #
#  Argument parsing
# --------------------------------------------------------------------------- #

INTEGRATION_ID="${IW_INTEGRATION_ID:-}"
WEBHOOK_SECRET="${IW_SECRET:-}"
VERSION="$DEFAULT_VERSION"
NO_START=0
UNINSTALL=0

parse_args() {
    while [ $# -gt 0 ]; do
        case "$1" in
            --integration-id)
                [ $# -ge 2 ] || fatal "--integration-id requires a value"
                INTEGRATION_ID="$2"; shift 2 ;;
            --secret)
                [ $# -ge 2 ] || fatal "--secret requires a value"
                WEBHOOK_SECRET="$2"; shift 2 ;;
            --version)
                [ $# -ge 2 ] || fatal "--version requires a value"
                VERSION="$2"; shift 2 ;;
            --no-start)
                NO_START=1; shift ;;
            --uninstall)
                UNINSTALL=1; shift ;;
            -h|--help)
                usage; exit 0 ;;
            *)
                fatal "Unknown option: $1" ;;
        esac
    done
}

usage() {
    cat <<'USAGE'
InariWatch Agent Installer

Usage:
  install.sh [OPTIONS]

Options:
  --integration-id <value>  Integration ID (from InariWatch dashboard)
  --secret <value>          Webhook secret (from InariWatch dashboard)
  --version <value>         Agent version to install (default: 0.1.0)
  --no-start                Install but do not start the service
  --uninstall               Remove agent, service, user, and config
  -h, --help                Show this help message

Environment variables:
  IW_INTEGRATION_ID         Same as --integration-id
  IW_SECRET                 Same as --secret
USAGE
}

# --------------------------------------------------------------------------- #
#  Pre-flight checks
# --------------------------------------------------------------------------- #

require_root() {
    if [ "$(id -u)" -ne 0 ]; then
        fatal "This installer must be run as root. Try: sudo sh install.sh"
    fi
}

detect_arch() {
    step "Detecting architecture"
    ARCH=$(uname -m)
    case "$ARCH" in
        x86_64)  ok "Architecture: x86_64" ;;
        aarch64) ok "Architecture: aarch64" ;;
        arm64)   ARCH="aarch64"; ok "Architecture: aarch64 (arm64)" ;;
        *)       fatal "Unsupported architecture: $ARCH. Only x86_64 and aarch64 are supported." ;;
    esac
}

check_kernel() {
    step "Verifying kernel version (>= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR})"
    KVER=$(uname -r)
    KMAJOR=$(printf '%s' "$KVER" | cut -d. -f1)
    KMINOR=$(printf '%s' "$KVER" | cut -d. -f2)

    if [ "$KMAJOR" -lt "$MIN_KERNEL_MAJOR" ] 2>/dev/null; then
        fatal "Kernel $KVER is too old. InariWatch requires kernel >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR} for BPF CO-RE support."
    fi

    if [ "$KMAJOR" -eq "$MIN_KERNEL_MAJOR" ] && [ "$KMINOR" -lt "$MIN_KERNEL_MINOR" ] 2>/dev/null; then
        fatal "Kernel $KVER is too old. InariWatch requires kernel >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR} for BPF CO-RE support."
    fi

    ok "Kernel version: $KVER"
}

check_btf() {
    step "Checking BTF availability"
    if [ ! -f /sys/kernel/btf/vmlinux ]; then
        err "BTF not found at /sys/kernel/btf/vmlinux"
        err ""
        err "BTF (BPF Type Format) is required for CO-RE (Compile Once, Run Everywhere)."
        err "Your kernel may need to be rebuilt with CONFIG_DEBUG_INFO_BTF=y."
        err ""
        err "On Ubuntu/Debian:  sudo apt install linux-image-\$(uname -r)"
        err "On Fedora/RHEL:    sudo dnf install kernel-devel"
        err ""
        err "See: https://docs.inariwatch.com/agent/requirements#btf"
        fatal "BTF is required."
    fi
    ok "BTF available at /sys/kernel/btf/vmlinux"
}

detect_downloader() {
    if command -v curl >/dev/null 2>&1; then
        DOWNLOADER="curl"
    elif command -v wget >/dev/null 2>&1; then
        DOWNLOADER="wget"
    else
        fatal "Neither curl nor wget found. Install one and retry."
    fi
    ok "Using $DOWNLOADER for downloads"
}

# --------------------------------------------------------------------------- #
#  Download helpers
# --------------------------------------------------------------------------- #

download() {
    url="$1"
    dest="$2"
    if [ "$DOWNLOADER" = "curl" ]; then
        curl -fsSL --retry 3 --retry-delay 2 -o "$dest" "$url"
    else
        wget -q --tries=3 --waitretry=2 -O "$dest" "$url"
    fi
}

# --------------------------------------------------------------------------- #
#  Install steps
# --------------------------------------------------------------------------- #

download_binary() {
    step "Downloading inariwatch-agent v${VERSION} (${ARCH})"

    BINARY_URL="${RELEASES_BASE}/v${VERSION}/${BINARY_NAME}-${ARCH}"
    CHECKSUM_URL="${BINARY_URL}.sha256"

    TMPDIR=$(mktemp -d -m 0700)
    trap 'rm -rf "$TMPDIR"' EXIT

    info "Downloading binary..."
    if ! download "$BINARY_URL" "${TMPDIR}/${BINARY_NAME}"; then
        err "Failed to download binary from:"
        err "  $BINARY_URL"
        err ""
        err "Check that version v${VERSION} exists and the URL is reachable."
        err "Available releases: https://github.com/orbita-pos/inariwatch-agent-releases/releases"
        fatal "Download failed."
    fi

    info "Downloading checksum..."
    if ! download "$CHECKSUM_URL" "${TMPDIR}/${BINARY_NAME}.sha256"; then
        err "Failed to download SHA256 checksum from:"
        err "  $CHECKSUM_URL"
        fatal "Checksum download failed."
    fi

    ok "Download complete"
}

verify_checksum() {
    step "Verifying SHA256 checksum"

    EXPECTED=$(awk '{print $1}' "${TMPDIR}/${BINARY_NAME}.sha256")
    ACTUAL=$(sha256sum "${TMPDIR}/${BINARY_NAME}" | awk '{print $1}')

    if [ "$EXPECTED" != "$ACTUAL" ]; then
        err "Checksum mismatch!"
        err "  Expected: $EXPECTED"
        err "  Got:      $ACTUAL"
        err ""
        err "The binary may have been corrupted or tampered with."
        err "Try downloading again, or verify manually."
        fatal "Checksum verification failed."
    fi

    ok "Checksum verified: $ACTUAL"
}

install_binary() {
    step "Installing binary to ${INSTALL_DIR}/${BINARY_NAME}"

    # C5 fix: atomic install — write temp file in target dir (same filesystem),
    # then rename. Prevents TOCTOU between /tmp and final location.
    TMPBIN="${INSTALL_DIR}/${BINARY_NAME}.new.$$"
    cp "${TMPDIR}/${BINARY_NAME}" "$TMPBIN"
    chmod 0755 "$TMPBIN"

    # Re-verify checksum immediately before atomic rename (belt-and-suspenders)
    FINAL_HASH=$(sha256sum "$TMPBIN" | awk '{print $1}')
    EXPECTED_HASH=$(awk '{print $1}' "${TMPDIR}/${BINARY_NAME}.sha256")
    if [ "$FINAL_HASH" != "$EXPECTED_HASH" ]; then
        rm -f "$TMPBIN"
        fatal "Pre-install checksum mismatch — aborting (possible tampering)"
    fi

    # Atomic rename (same filesystem guarantees atomicity)
    mv -f "$TMPBIN" "${INSTALL_DIR}/${BINARY_NAME}"
    ok "Binary installed (atomic rename)"
}

create_user() {
    step "Creating system user: ${SERVICE_USER}"

    useradd --system --no-create-home --shell /usr/sbin/nologin "$SERVICE_USER" 2>/dev/null || true

    if id "$SERVICE_USER" >/dev/null 2>&1; then
        ok "User '${SERVICE_USER}' ready"
    else
        fatal "Failed to create system user '${SERVICE_USER}'."
    fi
}

set_capabilities() {
    step "Setting BPF capabilities on binary"

    if ! command -v setcap >/dev/null 2>&1; then
        err "setcap not found. Install libcap2-bin (Debian/Ubuntu) or libcap (Fedora/RHEL)."
        fatal "Cannot set capabilities without setcap."
    fi

    setcap cap_bpf,cap_perfmon,cap_net_admin,cap_sys_resource+ep "${INSTALL_DIR}/${BINARY_NAME}"
    ok "Capabilities set: cap_bpf, cap_perfmon, cap_net_admin, cap_sys_resource"
}

write_config() {
    step "Writing configuration"

    mkdir -p "$CONFIG_DIR"

    # ---- agent.toml --------------------------------------------------------
    if [ -n "$INTEGRATION_ID" ]; then
        ID_LINE="integration_id = \"${INTEGRATION_ID}\""
    else
        ID_LINE="# integration_id = \"\""
    fi

    if [ -n "$WEBHOOK_SECRET" ]; then
        SECRET_LINE="webhook_secret = \"${WEBHOOK_SECRET}\""
    else
        SECRET_LINE="# webhook_secret = \"\""
    fi

    # Only write config if it doesn't already exist (preserve user edits)
    if [ -f "${CONFIG_DIR}/agent.toml" ]; then
        warn "Config file already exists — not overwriting: ${CONFIG_DIR}/agent.toml"
    else
        cat > "${CONFIG_DIR}/agent.toml" <<TOML
# InariWatch Agent Configuration
# Documentation: https://docs.inariwatch.com/agent/config

[cloud]
endpoint = "https://app.inariwatch.com/api/agent/events"
${ID_LINE}
${SECRET_LINE}

[agent]
log_level = "info"

[probes]
enable_process = true
enable_network = true
enable_filesystem = true
enable_dns = true
enable_tls = true
enable_syscall = true
enable_security = false
TOML

        ok "Config written: ${CONFIG_DIR}/agent.toml"
    fi

    # C6 fix: restrict config file permissions (contains webhook secret)
    chmod 0640 "${CONFIG_DIR}/agent.toml"
    chown root:inariwatch "${CONFIG_DIR}/agent.toml" 2>/dev/null || true
    if [ -n "$WEBHOOK_SECRET" ]; then
        warn "Webhook secret written to config — ensure permissions are secure"
    fi
    ok "Config files secured"
}

create_directories() {
    step "Creating data and runtime directories"

    mkdir -p "$DATA_DIR"
    chown -R "${SERVICE_USER}:${SERVICE_USER}" "$(dirname "$DATA_DIR")"
    ok "Data directory: $DATA_DIR"

    mkdir -p "$RUN_DIR"
    chown "${SERVICE_USER}:${SERVICE_USER}" "$RUN_DIR"
    ok "Runtime directory: $RUN_DIR"
}

install_service() {
    step "Installing systemd service"

    cat > "$SERVICE_FILE" <<'SERVICE'
[Unit]
Description=InariWatch eBPF Observability Agent
Documentation=https://docs.inariwatch.com/agent
After=network-online.target
Wants=network-online.target
ConditionPathExists=/sys/kernel/btf/vmlinux

[Service]
Type=notify
User=inariwatch
Group=inariwatch
ExecStart=/usr/local/bin/inariwatch-agent --config /etc/inariwatch/agent.toml
Restart=on-failure
RestartSec=5
StartLimitBurst=5
StartLimitIntervalSec=60

# Environment
EnvironmentFile=-/etc/inariwatch/agent.env

# BPF requires locked memory for maps
LimitMEMLOCK=infinity
LimitNOFILE=65536

# Capabilities (no root needed)
AmbientCapabilities=CAP_BPF CAP_PERFMON CAP_NET_ADMIN CAP_SYS_RESOURCE
CapabilityBoundingSet=CAP_BPF CAP_PERFMON CAP_NET_ADMIN CAP_SYS_RESOURCE

# Security hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
MemoryDenyWriteExecute=no
RestrictNamespaces=no

# BPF needs access to these
ReadWritePaths=/var/lib/inariwatch /var/run/inariwatch
ReadOnlyPaths=/sys/kernel/btf /sys/fs/bpf /proc

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=inariwatch-agent

# Watchdog
WatchdogSec=30
NotifyAccess=main

# OOM protection
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target
SERVICE

    ok "Service file written: $SERVICE_FILE"

    systemctl daemon-reload
    ok "systemd daemon reloaded"

    systemctl enable inariwatch-agent
    ok "Service enabled (starts on boot)"
}

start_service() {
    step "Starting InariWatch agent"

    systemctl start inariwatch-agent
    ok "Service started"

    printf '\n'
    systemctl status inariwatch-agent --no-pager || true
}

# --------------------------------------------------------------------------- #
#  Uninstall
# --------------------------------------------------------------------------- #

do_uninstall() {
    require_root

    step "Uninstalling InariWatch agent"

    # Stop and disable service
    if systemctl is-active inariwatch-agent >/dev/null 2>&1; then
        info "Stopping service..."
        systemctl stop inariwatch-agent
        ok "Service stopped"
    fi

    if systemctl is-enabled inariwatch-agent >/dev/null 2>&1; then
        info "Disabling service..."
        systemctl disable inariwatch-agent
        ok "Service disabled"
    fi

    # Remove service file
    if [ -f "$SERVICE_FILE" ]; then
        rm -f "$SERVICE_FILE"
        systemctl daemon-reload
        ok "Service file removed"
    fi

    # Remove binary
    if [ -f "${INSTALL_DIR}/${BINARY_NAME}" ]; then
        rm -f "${INSTALL_DIR}/${BINARY_NAME}"
        ok "Binary removed"
    fi

    # Remove config
    if [ -d "$CONFIG_DIR" ]; then
        rm -rf "$CONFIG_DIR"
        ok "Config directory removed"
    fi

    # Remove data
    if [ -d "$(dirname "$DATA_DIR")" ]; then
        rm -rf "$(dirname "$DATA_DIR")"
        ok "Data directory removed"
    fi

    # Remove run directory
    if [ -d "$RUN_DIR" ]; then
        rm -rf "$RUN_DIR"
        ok "Runtime directory removed"
    fi

    # Remove user
    if id "$SERVICE_USER" >/dev/null 2>&1; then
        userdel "$SERVICE_USER" 2>/dev/null || true
        ok "User '${SERVICE_USER}' removed"
    fi

    info "InariWatch agent has been completely uninstalled."
}

# --------------------------------------------------------------------------- #
#  Main
# --------------------------------------------------------------------------- #

main() {
    parse_args "$@"

    printf '%s\n' ""
    printf '%s\n' "  ╦┌┐┌┌─┐┬─┐┬┬ ┬┌─┐┌┬┐┌─┐┬ ┬"
    printf '%s\n' "  ║│││├─┤├┬┘│││├─┤ │ │  ├─┤"
    printf '%s\n' "  ╩┘└┘┴ ┴┴└─┴└┴┘┴ ┴ ┴ └─┘┴ ┴"
    printf '%s\n' "  eBPF Observability Agent Installer"
    printf '%s\n' ""

    # Handle uninstall early
    if [ "$UNINSTALL" -eq 1 ]; then
        do_uninstall
        exit 0
    fi

    require_root
    detect_arch
    check_kernel
    check_btf
    detect_downloader
    download_binary
    verify_checksum
    install_binary
    create_user
    set_capabilities
    write_config
    create_directories
    install_service

    if [ "$NO_START" -eq 1 ]; then
        info "Skipping service start (--no-start specified)."
        info "Start manually: systemctl start inariwatch-agent"
    else
        start_service
    fi

    # Final summary
    printf '\n'
    printf '%s%s══════════════════════════════════════════%s\n' "$BOLD" "$GREEN" "$RESET"
    printf '%s  InariWatch agent v%s installed successfully!%s\n' "$GREEN" "$VERSION" "$RESET"
    printf '%s%s══════════════════════════════════════════%s\n' "$BOLD" "$GREEN" "$RESET"
    printf '\n'
    info "Binary:  ${INSTALL_DIR}/${BINARY_NAME}"
    info "Config:  ${CONFIG_DIR}/agent.toml"
    info "Service: inariwatch-agent.service"
    printf '\n'

    if [ -z "$INTEGRATION_ID" ] || [ -z "$WEBHOOK_SECRET" ]; then
        warn "No cloud credentials configured."
        warn "Set integration_id and webhook_secret in ${CONFIG_DIR}/agent.toml"
        warn "  sudo systemctl restart inariwatch-agent"
        warn ""
        warn "Get credentials at: app.inariwatch.com → Integrations → eBPF Agent"
    fi
}

main "$@"
