#!/usr/bin/env bash
#
# zc — build-from-source installer.
#
# Run from a cloned `zakuro-ai/zc` checkout:
#
#     ./install.sh                 # build release binary, install to /usr/local/bin
#     ./install.sh --dir ~/.local/bin
#     ./install.sh -y              # install/update Rust via rustup without prompting
#
# It checks for a Rust toolchain (>= 1.85), installs one via rustup if missing
# (with -y, or after a prompt), runs `cargo build --release`, and installs the
# resulting binary to /usr/local/bin/zc (using sudo for that step) — or to the
# path given with --dir.
#
# No prebuilt binary and no Docker required. For the prebuilt one-line install,
# see: curl -fsSL https://get.zakuro-ai.com/zc | bash
#
set -euo pipefail

MIN_RUST="1.87"
TARGET_DIR=""
ASSUME_YES=0

# ---- pretty output ---------------------------------------------------------
if [ -t 1 ] && command -v tput >/dev/null 2>&1 && [ -n "$(tput colors 2>/dev/null || echo 0)" ]; then
    BOLD="$(tput bold)"; DIM="$(tput dim)"; RED="$(tput setaf 1)"; GREEN="$(tput setaf 2)"
    YELLOW="$(tput setaf 3)"; BLUE="$(tput setaf 4)"; RESET="$(tput sgr0)"
else
    BOLD=""; DIM=""; RED=""; GREEN=""; YELLOW=""; BLUE=""; RESET=""
fi
info()  { printf '%s→%s %s\n' "$BLUE" "$RESET" "$*"; }
ok()    { printf '%s✓%s %s\n' "$GREEN" "$RESET" "$*"; }
warn()  { printf '%s!%s %s\n' "$YELLOW" "$RESET" "$*" >&2; }
fatal() { printf '%sError:%s %s\n' "$RED" "$RESET" "$*" >&2; exit 1; }

# ---- args ------------------------------------------------------------------
while [ "$#" -gt 0 ]; do
    case "$1" in
        -d|--dir) [ -n "${2:-}" ] || fatal "missing path for $1"; TARGET_DIR="$2"; shift 2 ;;
        -y|--yes) ASSUME_YES=1; shift ;;
        -h|--help) sed -n '3,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
        *) fatal "unknown option: $1 (use --help)" ;;
    esac
done

cd "$(dirname "$0")"
[ -f Cargo.toml ] && grep -qE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"zc2"' Cargo.toml \
    || fatal "run this from a cloned zakuro-ai/zc checkout (no zc Cargo.toml here)."

# ---- Rust toolchain --------------------------------------------------------
# Put a rustup-managed toolchain ($HOME/.cargo/bin) ahead of any system Rust, so a
# too-old distro toolchain (e.g. /usr/bin/rustc 1.75) can't shadow the rustup one.
prefer_rustup_path() {
    [ -d "$HOME/.cargo/bin" ] || return 0
    [ "${PATH%%:*}" = "$HOME/.cargo/bin" ] && return 0
    PATH="$HOME/.cargo/bin:$PATH"; export PATH
}
rust_ok() {
    command -v cargo >/dev/null 2>&1 || return 1
    local v have need
    v="$(rustc --version 2>/dev/null | awk '{print $2}')"; [ -n "$v" ] || return 1
    have="$(printf '%s' "$v"        | awk -F. '{printf "%d%03d",$1,$2}')"
    need="$(printf '%s' "$MIN_RUST" | awk -F. '{printf "%d%03d",$1,$2}')"
    [ "$have" -ge "$need" ]
}
ensure_rust() {
    prefer_rustup_path
    rust_ok && { ok "Using $(rustc --version)"; return; }
    warn "zc needs Rust >= $MIN_RUST (found: $(rustc --version 2>/dev/null || echo 'no toolchain'))."
    local do_install="$ASSUME_YES"
    if [ "$ASSUME_YES" != "1" ] && { true >/dev/tty; } 2>/dev/null; then
        printf '  Install/update Rust via rustup now? [Y/n]: ' >/dev/tty
        local reply; read -r reply </dev/tty || reply=""
        case "$reply" in [Nn]*) do_install=0 ;; *) do_install=1 ;; esac
    fi
    [ "$do_install" = "1" ] || fatal "Rust >= $MIN_RUST is required. Install it (or re-run with -y):
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable"
    info "Installing the Rust toolchain via rustup…"
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --no-modify-path
    prefer_rustup_path
    command -v rustup >/dev/null 2>&1 && rustup default stable >/dev/null 2>&1 || true
    rust_ok || fatal "Rust is still < $MIN_RUST after rustup (a system toolchain may be shadowing it)."
    ok "Using $(rustc --version)"
}

# ---- C toolchain (linker) --------------------------------------------------
# rustup installs rustc+cargo but NOT a system linker/C compiler. Without `cc`,
# cargo can't link build scripts and fails early with
# "could not compile <crate> (build script)". A fresh Debian/Ubuntu has none, so
# install build-essential (+ pkg-config/libssl-dev for the openssl-linked deps).
ensure_build_toolchain() {
    command -v cc >/dev/null 2>&1 && return 0
    if ! command -v apt-get >/dev/null 2>&1; then
        warn "No C compiler (cc) found and this isn't a Debian/Ubuntu box. Install a C
toolchain + linker (e.g. Xcode Command Line Tools on macOS, or your distro's
'gcc'/'clang' + 'pkg-config'/'openssl' packages) before building."
        return 0
    fi
    info "Installing a C toolchain (build-essential) needed to link the build…"
    local sudo=""; [ "$(id -u)" -ne 0 ] && command -v sudo >/dev/null 2>&1 && sudo="sudo"
    export DEBIAN_FRONTEND=noninteractive
    # A freshly-booted cloud image runs apt-daily/unattended-upgrades, which hold
    # the apt lock; DPkg::Lock::Timeout doesn't cover the lists lock, so stop those
    # units first, then retry a few times against transient locks.
    $sudo systemctl stop apt-daily.timer apt-daily-upgrade.timer apt-daily.service \
        apt-daily-upgrade.service unattended-upgrades.service >/dev/null 2>&1 || true
    local i
    for i in 1 2 3 4 5; do
        $sudo apt-get -o DPkg::Lock::Timeout=300 update -qq >/dev/null 2>&1 || true
        if $sudo apt-get -o DPkg::Lock::Timeout=300 install -y -qq \
             build-essential pkg-config libssl-dev >/tmp/zc-apt.log 2>&1; then
            break
        fi
        [ "$i" = 5 ] && break
        sleep 5
    done
    if command -v cc >/dev/null 2>&1; then
        ok "C toolchain ready ($(cc --version 2>/dev/null | head -1))."
    else
        warn "could not auto-install build-essential (see /tmp/zc-apt.log). Install it
manually, then re-run:  sudo apt-get install -y build-essential pkg-config libssl-dev"
    fi
}

# ---- private transitive git deps -------------------------------------------
# zc depends on private crates fetched over git (zakuro-wire from zakuro-ai/zakuro,
# zakuro-client from zakuro-ai/zakuro-drive). By default cargo fetches these with
# its built-in git client, which cannot use your GitHub credentials and dies with
# "could not read Username for https://github.com". Tell cargo to shell out to the
# git CLI so it uses whatever auth you already have (gh credential helper, a
# stored PAT, or SSH). If the GitHub CLI is installed and authenticated, wire its
# credential helper up first.
ensure_private_dep_auth() {
    export CARGO_NET_GIT_FETCH_WITH_CLI=true
    if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
        gh auth setup-git >/dev/null 2>&1 || true
        ok "Using your GitHub CLI auth for private crate fetches."
    else
        info "Private crates are fetched with the git CLI (CARGO_NET_GIT_FETCH_WITH_CLI)."
        info "Make sure git can reach github.com — e.g. run 'gh auth login', or use SSH."
    fi
}

# ---- build + install -------------------------------------------------------
ensure_rust
ensure_build_toolchain
ensure_private_dep_auth
info "Building zc (${BOLD}cargo build --release${RESET} — this can take a few minutes)…"
if ! cargo build --release; then
    fatal "cargo build failed. If it stopped on a private git dependency
(zakuro-wire / zakuro-client → 'could not read Username for https://github.com'),
authenticate to GitHub first, then re-run:
    gh auth login            # or configure an SSH key / git credential helper
    ./install.sh"
fi
bin=""
for c in target/release/zc target/release/zc2; do [ -f "$c" ] && { bin="$c"; break; }; done
[ -n "$bin" ] || fatal "build finished but no zc binary found under target/release/."

dir="$TARGET_DIR" sudo=""
if [ -z "$dir" ]; then
    if [ -w /usr/local/bin ] 2>/dev/null; then dir="/usr/local/bin"
    elif command -v sudo >/dev/null 2>&1; then dir="/usr/local/bin"; sudo="sudo"
    else dir="$HOME/.local/bin"; fi
fi
mkdir -p "$dir" 2>/dev/null || { [ -n "$sudo" ] && $sudo mkdir -p "$dir"; } || fatal "cannot create $dir"
info "Installing to ${BOLD}$dir/zc${RESET}…"
if [ -w "$dir" ]; then install -m 0755 "$bin" "$dir/zc"
elif [ -n "$sudo" ]; then $sudo install -m 0755 "$bin" "$dir/zc"
else fatal "no write permission for $dir (re-run with --dir \$HOME/.local/bin)."; fi
ok "zc installed to $dir/zc"
case ":$PATH:" in
    *":$dir:"*) : ;;
    *) warn "$dir is not on your PATH. Add it:"; printf '    export PATH="%s:$PATH"\n' "$dir" >&2 ;;
esac
command -v zc >/dev/null 2>&1 && printf '\n%s\n' "$("$dir/zc" --version 2>/dev/null | head -1)"
