#!/bin/bash
# install-kvcs.sh — KVCacheStore interactive installer (self-contained)
#
# Supports: CentOS 7/8, Ubuntu 20.04/22.04/24.04
# Package types: tarball, rpm, deb
# Components: kvcs-efc (server) + kvcs-sdk (Python/Go/Rust/C)
#
# Non-interactive mode: set KVCS_NON_INTERACTIVE=1 and provide env vars.
# See --help for full usage.
#
# Quick install:
#   curl -fsSL https://kvcachestore.oss-accelerate.aliyuncs.com/scripts/install-kvcs.sh | bash

set -euo pipefail

# When run via "curl | bash", stdin is consumed by curl.
# Redirect interactive reads from /dev/tty so prompts still work.
# Fall back to stdin when no controlling tty exists (CI / non-interactive).
if [[ -t 0 ]]; then
    exec 3<&0
elif (exec 3</dev/tty) 2>/dev/null; then
    exec 3</dev/tty
else
    exec 3<&0
fi

# ============================================================
#  Constants & Defaults
# ============================================================
SCRIPT_VERSION="1.0.0"
# Replaced by scripts/ci/package-install-script.sh for the published copy.
# A source checkout falls back to the repository VERSION file.
DEFAULT_VERSION="0.5.1"
DEFAULT_BASE_URL="https://kvcachestore.oss-accelerate.aliyuncs.com"
DEFAULT_INSTALL_DIR="/opt/kvcs"
DEFAULT_CONFIG_DIR="/etc/kvcs"
DEFAULT_LOG_DIR="/var/log/kvcs/server"
DEFAULT_METRICS_ADDR="127.0.0.1:9093"
DEFAULT_BACKEND="kvcachestore"

# ============================================================
#  Color output helpers
# ============================================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m' # No Color

info()    { echo -e "${GREEN}[INFO]${NC} $*"; }
warn()    { echo -e "${YELLOW}[WARN]${NC} $*"; }
error()   { echo -e "${RED}[ERROR]${NC} $*"; }
banner()  { echo -e "\n${BLUE}${BOLD}$*${NC}"; }
die()     { error "$*"; exit 1; }

# ============================================================
#  Usage / Help
# ============================================================
usage() {
    cat <<'EOF'
install-kvcs.sh — KVCacheStore Interactive Installer

Usage:
  bash install-kvcs.sh [OPTIONS]

Options:
  --help, -h            Show this help message
  --non-interactive     Run in non-interactive mode (read config from env vars)
  --dry-run             Show what would be done without executing

Non-interactive environment variables:
  KVCS_VERSION          Version to install (default: version embedded by the release pipeline)
  KVCS_BASE_URL         Override download base URL (flat layout, e.g. aone CI link;
                        default: https://kvcachestore.oss-accelerate.aliyuncs.com with packages/ & sdk/ subpaths)
  KVCS_PKG_TYPE         Package type: tarball|rpm|deb (default: auto-detect)
  KVCS_BACKEND          Backend type: kvcachestore|mem|disk|diskless (default: kvcachestore)
  KVCS_MOUNTPOINTS      Comma-separated kvcachestore v1 mountpoint addresses (mountpoint_index auto-assigned 1..N)
  KVCS_DEFAULT_MOUNTPOINT_INDEX  Which mountpoint_index is the default/active entry (default: 1)
  KVCS_LOG_DIR          Log directory (default: /var/log/kvcs/server)
  KVCS_METRICS_ADDR     Metrics listen address (default: 127.0.0.1:9093, "none" to disable)
  KVCS_INSTALL_SDK      Fetch SDK package to /opt/kvcs-sdk/<version> (+latest symlink): y|n (default: y)
  KVCS_START_SERVICE    Start service after install: y|n (default: y)
  KVCS_INSTALL_DIR      Tarball install directory (default: /opt/kvcs)
  KVCS_NON_INTERACTIVE  Set to 1 for non-interactive mode

Examples:
  # Interactive install
  bash install-kvcs.sh

  # Non-interactive install with a kvcachestore v1 mountpoint
  KVCS_NON_INTERACTIVE=1 \
  KVCS_MOUNTPOINTS="kvcs-001.cn-hangzhou.kvcachestore.aliyuncs.com" \
  bash install-kvcs.sh

  # Dry-run to see what would happen
  bash install-kvcs.sh --dry-run
EOF
    exit 0
}

# ============================================================
#  Argument parsing
# ============================================================
NON_INTERACTIVE="${KVCS_NON_INTERACTIVE:-0}"
DRY_RUN=0

while [[ $# -gt 0 ]]; do
    case "$1" in
        --help|-h) usage ;;
        --non-interactive) NON_INTERACTIVE=1; shift ;;
        --dry-run) DRY_RUN=1; shift ;;
        *) die "Unknown option: $1. Use --help for usage." ;;
    esac
done

# ============================================================
#  Utility: prompt with default value
# ============================================================
prompt() {
    local varname="$1" prompt_text="$2" default="$3"
    if [[ "$NON_INTERACTIVE" == "1" ]]; then
        printf -v "$varname" '%s' "$default"
        return
    fi
    local input
    echo -en "${YELLOW}?${NC} ${prompt_text} [${default}]: "
    read -r input <&3
    printf -v "$varname" '%s' "${input:-$default}"
}

prompt_yn() {
    local varname="$1" prompt_text="$2" default="$3"
    if [[ "$NON_INTERACTIVE" == "1" ]]; then
        printf -v "$varname" '%s' "$default"
        return
    fi
    local input
    echo -en "${YELLOW}?${NC} ${prompt_text} (y/n) [${default}]: "
    read -r input <&3
    input="${input:-$default}"
    case "$input" in
        [yY]*) printf -v "$varname" '%s' "y" ;;
        *)     printf -v "$varname" '%s' "n" ;;
    esac
}

# ============================================================
#  OS Detection
# ============================================================
detect_os() {
    if [[ -f /etc/os-release ]]; then
        . /etc/os-release
        OS_ID="${ID:-unknown}"
        OS_VERSION="${VERSION_ID:-unknown}"
    elif [[ -f /etc/centos-release ]]; then
        OS_ID="centos"
        OS_VERSION=$(grep -oE '[0-9]+' /etc/centos-release | head -1)
    else
        OS_ID="unknown"
        OS_VERSION="unknown"
    fi

    case "$OS_ID" in
        centos|rhel|alinux|anolis|rocky|alma|fedora)
            OS_FAMILY="rhel"
            RECOMMENDED_PKG="rpm"
            ;;
        ubuntu|debian)
            OS_FAMILY="debian"
            RECOMMENDED_PKG="deb"
            ;;
        *)
            OS_FAMILY="unknown"
            RECOMMENDED_PKG="tarball"
            ;;
    esac
}

# ============================================================
#  Architecture detection and package naming
# ============================================================
detect_arch() {
    # Tests may pass a machine value explicitly.  Production callers omit it,
    # so package selection cannot be influenced by an ambient environment
    # variable.
    local machine="${1:-$(uname -m)}"
    case "$machine" in
        x86_64|amd64)
            PACKAGE_ARCH="x86_64"
            RPM_ARCH="x86_64"
            DEB_ARCH="amd64"
            ;;
        aarch64|arm64)
            PACKAGE_ARCH="aarch64"
            RPM_ARCH="aarch64"
            DEB_ARCH="arm64"
            ;;
        *)
            die "Unsupported machine architecture: $machine. Expected x86_64 or aarch64."
            ;;
    esac
}

resolve_install_version() {
    if [[ -n "${KVCS_VERSION:-}" ]]; then
        INSTALL_VERSION="$KVCS_VERSION"
        return
    fi
    if [[ "$DEFAULT_VERSION" != __KVCS_* ]]; then
        INSTALL_VERSION="$DEFAULT_VERSION"
        return
    fi

    local script_dir version_file
    script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    version_file="${script_dir}/../VERSION"
    if [[ -f "$version_file" ]]; then
        INSTALL_VERSION="$(tr -d '[:space:]' <"$version_file")"
        return
    fi

    die "This installer has no embedded version. Set KVCS_VERSION explicitly."
}

resolve_package_names() {
    case "$PKG_TYPE" in
        tarball)
            EFC_PKG="kvcs-deploy-${INSTALL_VERSION}-${PACKAGE_ARCH}.tar.gz"
            ;;
        rpm)
            EFC_PKG="kvcs-efc-${INSTALL_VERSION}-1.${RPM_ARCH}.rpm"
            ;;
        deb)
            EFC_PKG="kvcs-efc_${INSTALL_VERSION}_${DEB_ARCH}.deb"
            ;;
        *)
            die "Invalid package type: $PKG_TYPE. Must be tarball, rpm, or deb."
            ;;
    esac
    SDK_PKG="kvcs-sdk-${INSTALL_VERSION}-${PACKAGE_ARCH}.tar.gz"
}

# ============================================================
#  Prerequisite checks
# ============================================================
check_prerequisites() {
    banner "[1/7] Checking prerequisites..."
    local errors=0

    # Check download tool
    if command -v curl >/dev/null 2>&1; then
        DOWNLOAD_CMD="curl"
    elif command -v wget >/dev/null 2>&1; then
        DOWNLOAD_CMD="wget"
    else
        error "Neither curl nor wget found. Please install one of them."
        errors=$((errors + 1))
    fi

    # Check systemd
    if ! command -v systemctl >/dev/null 2>&1; then
        error "systemctl not found. This installer requires systemd."
        errors=$((errors + 1))
    fi

    # Check root
    if [[ $EUID -ne 0 ]]; then
        warn "Not running as root. Some operations will require sudo."
        if ! command -v sudo >/dev/null 2>&1; then
            error "sudo not found and not running as root."
            errors=$((errors + 1))
        fi
    fi

    # Check tar
    if ! command -v tar >/dev/null 2>&1; then
        error "tar not found. Required for package extraction."
        errors=$((errors + 1))
    fi

    if [[ $errors -gt 0 ]]; then
        die "Prerequisites check failed with $errors error(s). Fix the issues above and retry."
    fi

    info "All prerequisites satisfied."
}

# ============================================================
#  Download helper
# ============================================================
download_file() {
    local url="$1" dest="$2"

    # Handle local path (file:// or absolute path)
    if [[ "$url" == /* ]]; then
        if [[ -f "$url" ]]; then
            cp "$url" "$dest"
            return 0
        else
            error "Local file not found: $url"
            return 1
        fi
    fi

    if [[ "$DOWNLOAD_CMD" == "curl" ]]; then
        curl -fSL --progress-bar -o "$dest" "$url"
    else
        wget --progress=bar:force -O "$dest" "$url"
    fi
}

# Try to verify sha256 checksum if .sha256 file exists
verify_checksum() {
    local file="$1" url_base="$2" filename="$3"
    local sha_file="${file}.sha256"
    local sha_url="${url_base}/${filename}.sha256"

    # Try to download checksum file (non-fatal if missing)
    if download_file "$sha_url" "$sha_file" 2>/dev/null; then
        local expected actual
        expected=$(awk '{print $1}' "$sha_file")
        actual=$(sha256sum "$file" | awk '{print $1}')
        if [[ "$expected" == "$actual" ]]; then
            info "  Checksum verified: $filename"
        else
            error "  Checksum mismatch for $filename!"
            error "  Expected: $expected"
            error "  Got:      $actual"
            rm -f "$file" "$sha_file"
            return 1
        fi
        rm -f "$sha_file"
    else
        warn "  No .sha256 file available, skipping checksum verification."
    fi
}

# ============================================================
#  Interactive configuration collection
# ============================================================
collect_config() {
    banner "KVCacheStore Installer v${SCRIPT_VERSION}"
    echo ""
    info "Detected OS: ${OS_ID} ${OS_VERSION} (${OS_FAMILY})"
    info "Recommended package type: ${RECOMMENDED_PKG}"
    echo ""
    echo "Press Enter to accept default values shown in brackets."
    echo ""

    # Version — no prompt (env override via KVCS_VERSION)
    resolve_install_version

    # Download URL — fixed accelerate endpoint, no prompt (env override via KVCS_BASE_URL)
    # Official OSS layout: packages under packages/, SDK under sdk/.
    # Custom source (KVCS_BASE_URL set, e.g. aone CI auto-generated link): flat layout,
    # all files directly under the given URL.
    BASE_URL="${KVCS_BASE_URL:-$DEFAULT_BASE_URL}"
    if [[ -n "${KVCS_BASE_URL:-}" ]]; then
        EFC_URL_PREFIX="${BASE_URL}"
        SDK_URL_PREFIX="${BASE_URL}"
    else
        EFC_URL_PREFIX="${BASE_URL}/packages"
        SDK_URL_PREFIX="${BASE_URL}/sdk"
    fi

    # Package type — auto-detect, no prompt
    PKG_TYPE="${KVCS_PKG_TYPE:-$RECOMMENDED_PKG}"
    case "$PKG_TYPE" in
        tarball|rpm|deb) ;;
        *) die "Invalid package type: $PKG_TYPE. Must be tarball, rpm, or deb." ;;
    esac
    resolve_package_names

    # Backend type — default kvcachestore, no prompt
    BACKEND="${KVCS_BACKEND:-$DEFAULT_BACKEND}"
    case "$BACKEND" in
        kvcachestore|mem|disk|diskless) ;;
        *) die "Invalid backend: $BACKEND. Must be kvcachestore, mem, disk, or diskless." ;;
    esac

    # KVCacheStore mountpoints (only for kvcachestore backend)
    # Multiple entries supported (address + index + default flag). All entries are
    # written to the `mountpoints:` list in efc.yaml; the entry marked as
    # default gets `default: true` (requests with mountpoint_index=0 route to it).
    MP_ADDRS=()
    MP_INDEXES=()
    MP_DEFAULTS=()
    MOUNTPOINT_ID=""       # resolved default mountpoint address (from default entry)
    DEFAULT_MP_POS=-1      # index into MP_* arrays for the default entry
    if [[ "$BACKEND" == "kvcachestore" ]]; then
        if [[ "$NON_INTERACTIVE" == "1" ]]; then
            # KVCS_MOUNTPOINTS: comma-separated addresses; mountpoint_index auto-assigned 1..N.
            # KVCS_DEFAULT_MOUNTPOINT_INDEX selects the default entry by index (default: 1).
            local env_mounts="${KVCS_MOUNTPOINTS:-}"
            if [[ -n "$env_mounts" ]]; then
                local -a mp_arr
                IFS=',' read -ra mp_arr <<< "$env_mounts"
                local i
                for i in "${!mp_arr[@]}"; do
                    MP_ADDRS+=("${mp_arr[$i]}")
                    MP_INDEXES+=("$((i + 1))")
                    MP_DEFAULTS+=("n")
                done
                local want_default="${KVCS_DEFAULT_MOUNTPOINT_INDEX:-1}"
                for i in "${!MP_INDEXES[@]}"; do
                    if [[ "${MP_INDEXES[$i]}" == "$want_default" ]]; then
                        MP_DEFAULTS[$i]="y"
                        break
                    fi
                done
            fi
        else
            echo ""
            info "Enter kvcachestore v1 mountpoints (empty address to finish):"
            info "  Format: kvcs-xxx.region.kvcachestore.aliyuncs.com"
            local next_index=1
            while true; do
                local mp_addr mp_index mp_def
                echo -en "${YELLOW}?${NC} Mountpoint #${next_index} address (empty to finish): "
                read -r mp_addr <&3
                [[ -z "$mp_addr" ]] && break
                prompt mp_index "  mountpoint_index for this mountpoint" "$next_index"
                if ! [[ "$mp_index" =~ ^[1-9][0-9]*$ ]]; then
                    die "Invalid mountpoint_index: '$mp_index'. Must be a positive integer."
                fi
                local dup
                for dup in "${MP_INDEXES[@]:-}"; do
                    [[ "$dup" == "$mp_index" ]] && die "Duplicate mountpoint_index: $mp_index"
                done
                prompt_yn mp_def "  set as default?" "$([[ ${#MP_ADDRS[@]} -eq 0 ]] && echo y || echo n)"
                MP_ADDRS+=("$mp_addr")
                MP_INDEXES+=("$mp_index")
                MP_DEFAULTS+=("$mp_def")
                next_index=$((mp_index + 1))
            done
        fi

        # Resolve the default entry
        if [[ ${#MP_ADDRS[@]} -gt 0 ]]; then
            local i default_count=0
            for i in "${!MP_DEFAULTS[@]}"; do
                if [[ "${MP_DEFAULTS[$i]}" == "y" ]]; then
                    default_count=$((default_count + 1))
                    DEFAULT_MP_POS=$i
                fi
            done
            if [[ $default_count -gt 1 ]]; then
                die "At most one mountpoint can be marked as default (got ${default_count})."
            fi
            if [[ $default_count -eq 0 ]]; then
                DEFAULT_MP_POS=0
                MP_DEFAULTS[0]="y"
                info "No default marked; using first entry (mountpoint_index=${MP_INDEXES[0]}) as default."
            fi
            MOUNTPOINT_ID="${MP_ADDRS[$DEFAULT_MP_POS]}"
        fi

        if [[ -z "$MOUNTPOINT_ID" ]]; then
            warn "No mountpoint specified. You must configure the mountpoints list in efc.yaml before starting."
        fi
    fi

    # Log directory — use default, no prompt
    LOG_DIR="${KVCS_LOG_DIR:-$DEFAULT_LOG_DIR}"

    # Metrics address — use default, no prompt
    METRICS_ADDR="${KVCS_METRICS_ADDR:-$DEFAULT_METRICS_ADDR}"
    if [[ "$METRICS_ADDR" == "none" ]]; then
        METRICS_ADDR=""
    fi

    # Fetch SDK — always yes, no prompt (placed at /opt/kvcs-sdk/<version>, not installed)
    INSTALL_SDK="${KVCS_INSTALL_SDK:-y}"

    # Start service — always yes, no prompt
    START_SERVICE="${KVCS_START_SERVICE:-y}"

    # Install directory (tarball only)
    if [[ "$PKG_TYPE" == "tarball" ]]; then
        local env_install_dir="${KVCS_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
        prompt INSTALL_DIR "Installation directory" "$env_install_dir"
    else
        INSTALL_DIR="$DEFAULT_INSTALL_DIR"
    fi

    # Summary
    echo ""
    banner "Installation Summary"
    echo "  Version:        ${INSTALL_VERSION}"
    echo "  Architecture:   ${PACKAGE_ARCH}"
    echo "  Package:        ${PKG_TYPE} (${OS_ID} ${OS_VERSION})"
    # Display name for backend in user-facing output (avoid internal names)
    BACKEND_DISPLAY="$BACKEND"
    [[ "$BACKEND" == "kvcachestore" ]] && BACKEND_DISPLAY="kvcachestore v1"
    echo "  Backend:        ${BACKEND_DISPLAY}"
    if [[ "$BACKEND" == "kvcachestore" && ${#MP_ADDRS[@]} -gt 0 ]]; then
        echo "  Mountpoints:"
        local _i
        for _i in "${!MP_ADDRS[@]}"; do
            local _mark=""
            [[ "${MP_DEFAULTS[$_i]}" == "y" ]] && _mark=" (default)"
            echo "    - mountpoint_index=${MP_INDEXES[$_i]}  ${MP_ADDRS[$_i]}${_mark}"
        done
    fi
    echo ""
    echo "  Downloads:"
    echo "    EFC: ${EFC_URL_PREFIX}/${EFC_PKG}"
    echo "    SDK: ${SDK_URL_PREFIX}/${SDK_PKG}"
    echo ""
    echo "  Install paths:"
    if [[ "$PKG_TYPE" == "tarball" ]]; then
        echo "    EFC:    ${INSTALL_DIR}/"
    else
        echo "    EFC:    /usr/bin/kvcs_efc_server"
    fi
    echo "    SDK:    /opt/kvcs-sdk/${INSTALL_VERSION}/  (latest -> this version)"
    echo "    Config: /etc/kvcs/efc.yaml"
    echo "    Logs:   ${LOG_DIR}"
    echo ""

    if [[ "$NON_INTERACTIVE" != "1" && "$DRY_RUN" != "1" ]]; then
        echo -en "${YELLOW}?${NC} Proceed with installation? (y/n) [y]: "
        local confirm
        read -r confirm <&3
        confirm="${confirm:-y}"
        if [[ "$confirm" != [yY]* ]]; then
            info "Installation cancelled."
            exit 0
        fi
    fi
}

# ============================================================
#  Step 2: Download packages
# ============================================================
download_packages() {
    banner "[2/7] Downloading packages..."

    TMPDIR=$(mktemp -d /tmp/kvcs-install.XXXXXX)
    trap 'rm -rf "$TMPDIR"' EXIT

    # Download EFC package
    local efc_url="${EFC_URL_PREFIX}/${EFC_PKG}"
    info "Downloading EFC package: ${EFC_PKG}"
    if [[ "$DRY_RUN" == "1" ]]; then
        info "  [dry-run] Would download: ${efc_url}"
    else
        download_file "${efc_url}" "${TMPDIR}/${EFC_PKG}" || \
            die "Failed to download ${EFC_PKG} from ${efc_url}\n  Check that the URL is correct and the file exists."
        verify_checksum "${TMPDIR}/${EFC_PKG}" "${EFC_URL_PREFIX}" "$EFC_PKG"
        info "  Downloaded: ${EFC_PKG}"
    fi

    # Download SDK package
    if [[ "$INSTALL_SDK" == "y" ]]; then
        local sdk_url="${SDK_URL_PREFIX}/${SDK_PKG}"
        info "Downloading SDK package: ${SDK_PKG}"
        if [[ "$DRY_RUN" == "1" ]]; then
            info "  [dry-run] Would download: ${sdk_url}"
        else
            download_file "${sdk_url}" "${TMPDIR}/${SDK_PKG}" || \
                die "Failed to download ${SDK_PKG} from ${sdk_url}\n  Check that the URL is correct and the file exists."
            verify_checksum "${TMPDIR}/${SDK_PKG}" "${SDK_URL_PREFIX}" "$SDK_PKG"
            info "  Downloaded: ${SDK_PKG}"
        fi
    fi

    info "All packages downloaded."
}

# ============================================================
#  Step 3: Install EFC
# ============================================================
install_efc() {
    banner "[3/7] Installing kvcs-efc..."

    if [[ "$DRY_RUN" == "1" ]]; then
        case "$PKG_TYPE" in
            tarball) info "[dry-run] Would extract to ${INSTALL_DIR}" ;;
            rpm)     info "[dry-run] Would run: rpm -ivh ${EFC_PKG}" ;;
            deb)     info "[dry-run] Would run: dpkg -i ${EFC_PKG}" ;;
        esac
        return
    fi

    case "$PKG_TYPE" in
        tarball)
            # Extract tarball
            info "Extracting to ${INSTALL_DIR}..."
            sudo mkdir -p "$INSTALL_DIR"
            tar xzf "${TMPDIR}/${EFC_PKG}" -C "${TMPDIR}/"
            # The tarball extracts to kvcs-deploy-<version>/
            local extracted_dir="${TMPDIR}/kvcs-deploy-${INSTALL_VERSION}"
            if [[ ! -d "$extracted_dir" ]]; then
                # Try without version suffix
                extracted_dir=$(find "$TMPDIR" -maxdepth 1 -type d -name 'kvcs-deploy*' | head -1)
            fi
            if [[ -z "$extracted_dir" || ! -d "$extracted_dir" ]]; then
                die "Extracted directory not found. Tarball may have unexpected layout."
            fi
            sudo cp -af "$extracted_dir"/* "$INSTALL_DIR/"
            sudo chmod +x "$INSTALL_DIR/kvcs-ctl" "$INSTALL_DIR/kvcs_efc_server" 2>/dev/null || true

            KVCS_CTL="$INSTALL_DIR/kvcs-ctl"
            EFC_CONFIG_PATH="$INSTALL_DIR/efc.yaml"
            info "Installed to: ${INSTALL_DIR}"
            ;;
        rpm)
            info "Installing RPM package..."
            sudo rpm -ivh --replacepkgs "${TMPDIR}/${EFC_PKG}" || \
                die "RPM installation failed. Check package compatibility."
            KVCS_CTL="/usr/bin/kvcs-ctl"
            EFC_CONFIG_PATH="/etc/kvcs/efc.yaml"
            info "RPM installed successfully."
            ;;
        deb)
            info "Installing DEB package..."
            sudo dpkg -i "${TMPDIR}/${EFC_PKG}" || {
                warn "dpkg reported issues, attempting to fix dependencies..."
                sudo apt-get install -f -y 2>/dev/null || true
            }
            KVCS_CTL="/usr/bin/kvcs-ctl"
            EFC_CONFIG_PATH="/etc/kvcs/efc.yaml"
            info "DEB installed successfully."
            ;;
    esac
}

# ============================================================
#  Step 4: Generate efc.yaml
# ============================================================
generate_config() {
    banner "[4/7] Generating efc.yaml..."

    local config_dir
    if [[ "$PKG_TYPE" == "tarball" ]]; then
        config_dir="$INSTALL_DIR"
    else
        config_dir="$DEFAULT_CONFIG_DIR"
        sudo mkdir -p "$config_dir"
    fi
    EFC_CONFIG_PATH="${config_dir}/efc.yaml"

    if [[ "$DRY_RUN" == "1" ]]; then
        info "[dry-run] Would generate config at: ${EFC_CONFIG_PATH}"
        info "[dry-run] Backend: ${BACKEND_DISPLAY:-$BACKEND}"
        return
    fi

    # Build the mountpoints YAML list from everything the user entered.
    # The server requires a non-empty mountpoints list when backend=kvcachestore;
    # the default entry is marked with `default: true`.
    local mountpoints_block=""
    if [[ "$BACKEND" == "kvcachestore" && ${#MP_ADDRS[@]} -gt 0 ]]; then
        mountpoints_block="mountpoints:"
        local _i
        for _i in "${!MP_ADDRS[@]}"; do
            mountpoints_block+="
  - mountpoint_index: ${MP_INDEXES[$_i]}
    mountpoint_id: \"${MP_ADDRS[$_i]}\""
            if [[ "${MP_DEFAULTS[$_i]}" == "y" ]]; then
                mountpoints_block+="
    default: true"
            fi
        done
    else
        mountpoints_block="# mountpoints:
#   - mountpoint_index: 1
#     mountpoint_id: \"kvcs-xxx.region.kvcachestore.aliyuncs.com\"
#     default: true
#   - mountpoint_index: 2
#     mountpoint_id: \"kvcs-xxx.region.kvcachestore.aliyuncs.com\""
    fi

    # Metrics addr (empty = disabled; omitted = default 127.0.0.1:9093)
    local metrics_line=""
    if [[ -n "$METRICS_ADDR" ]]; then
        metrics_line="metrics_addr: \"${METRICS_ADDR}\""
    else
        metrics_line="# metrics_addr: \"127.0.0.1:9093\"  # Uncomment to enable Prometheus metrics"
    fi

    # Generate log directory
    sudo mkdir -p "$LOG_DIR" 2>/dev/null || true

    # Write to a temp file in the target directory first (same filesystem, so
    # the final mv is an atomic rename), validate it with the server binary,
    # then move it into place. A failed validation leaves any existing config
    # untouched and keeps the rejected file under a readable
    # efc.yaml.rejected-<timestamp> name.
    local tmp_config
    tmp_config=$(sudo mktemp "${config_dir}/.efc.yaml.XXXXXX") || \
        die "Failed to create temporary config file in ${config_dir}"
    # mktemp creates 0600; the installed config should be world-readable.
    sudo chmod 644 "$tmp_config"
    sudo tee "$tmp_config" >/dev/null <<EOF
# kvcs-efc configuration
# Generated by install-kvcs.sh at $(date -u +%Y-%m-%dT%H:%M:%SZ)

# Storage backend: kvcachestore | mem | disk | diskless
backend: ${BACKEND}

# KVCacheStore mountpoint list (required when backend=kvcachestore)
#   mountpoint_index: filesystem index (uint32, unique, must be >0); clients
#                   reference it via the io option mountpoint_index (0 = default entry)
#   mountpoint_id:  kvcachestore v1 mountpoint address,
#                   format: kvcs-xxx.region.kvcachestore.aliyuncs.com
#   default:        whether this entry is the default filesystem (at most one true;
#                   first entry is the default when none is marked)
${mountpoints_block}

# gRPC UDS socket path
grpc_socket: /var/run/kvcs/efc-grpc.sock

# Log configuration
log_dir: ${LOG_DIR}
log_level: info
log_max_size: 524288000
log_max_files: 10

# Prometheus metrics HTTP listen address (empty or omitted = disabled)
${metrics_line}
EOF

    validate_and_install_config "$tmp_config"
}

# ============================================================
#  Config precheck: validate the generated file with the server binary
#  before replacing the live config. Binaries without --check-config
#  support (older releases) fall back to installing without validation.
# ============================================================
validate_and_install_config() {
    local tmp_config="$1"

    local efc_bin
    if [[ "$PKG_TYPE" == "tarball" ]]; then
        efc_bin="$INSTALL_DIR/kvcs_efc_server"
    else
        efc_bin="/usr/bin/kvcs_efc_server"
    fi

    if [[ ! -x "$efc_bin" ]]; then
        # Binary not installed yet (first-time install) — skip validation.
        warn "Server binary not found at ${efc_bin}; skipping config validation."
        sudo mv -f "$tmp_config" "$EFC_CONFIG_PATH"
        info "Configuration written to: ${EFC_CONFIG_PATH}"
        return
    fi

    # Verify the binary can actually execute (e.g. missing shared libs → error).
    local version_out
    if ! version_out=$("$efc_bin" --version 2>&1); then
        # Cannot run at all — likely a broken install or missing dependencies.
        sudo rm -f "$tmp_config" 2>/dev/null || true
        die "Server binary at ${efc_bin} is not runnable (missing libraries?): ${version_out}"
    fi

    if ! "$efc_bin" --help 2>&1 | grep -q -- '--check-config'; then
        warn "Installed server binary does not support --check-config; skipping config validation."
        sudo mv -f "$tmp_config" "$EFC_CONFIG_PATH"
        info "Configuration written to: ${EFC_CONFIG_PATH}"
        return
    fi

    info "Validating generated configuration..."
    local check_output
    if check_output=$(sudo "$efc_bin" --check-config="$tmp_config" 2>&1); then
        sudo mv -f "$tmp_config" "$EFC_CONFIG_PATH"
        info "Configuration validated and installed to: ${EFC_CONFIG_PATH}"
    else
        # Keep the rejected file under a readable name so users can tell at a
        # glance what it is (multiple failures accumulate; no rotation).
        local rejected="${EFC_CONFIG_PATH}.rejected-$(date +%Y%m%d-%H%M%S)"
        sudo mv -f "$tmp_config" "$rejected" 2>/dev/null || rejected="$tmp_config"
        error "Generated configuration failed validation:"
        printf '%s\n' "$check_output" >&2
        error "Existing configuration (if any) was left untouched: ${EFC_CONFIG_PATH}"
        error "The rejected file is kept for inspection: ${rejected}"
        exit 1
    fi
}

# ============================================================
#  Step 5: Install Python SDK
# ============================================================
install_sdk() {
    if [[ "$INSTALL_SDK" != "y" ]]; then
        banner "[5/7] Skipping SDK fetch (user opted out)."
        return
    fi

    banner "[5/7] Fetching SDK package..."

    if [[ "$DRY_RUN" == "1" ]]; then
        info "[dry-run] Would extract SDK to /opt/kvcs-sdk/${INSTALL_VERSION}/ and update /opt/kvcs-sdk/latest symlink"
        return
    fi

    # Extract SDK tarball
    tar xzf "${TMPDIR}/${SDK_PKG}" -C "${TMPDIR}/"
    local sdk_dir="${TMPDIR}/kvcs-sdk-${INSTALL_VERSION}"
    if [[ ! -d "$sdk_dir" ]]; then
        sdk_dir=$(find "$TMPDIR" -maxdepth 1 -type d -name 'kvcs-sdk*' | head -1)
    fi

    if [[ -z "$sdk_dir" || ! -d "$sdk_dir" ]]; then
        warn "SDK directory not found after extraction. Skipping SDK fetch."
        return
    fi

    # Place SDK under /opt/kvcs-sdk/<version>/ (no installation — user installs
    # on demand), and point /opt/kvcs-sdk/latest at the newly fetched version.
    local sdk_root="/opt/kvcs-sdk"
    local sdk_install_dir="${sdk_root}/${INSTALL_VERSION}"
    local sdk_latest_link="${sdk_root}/latest"
    sudo mkdir -p "$sdk_install_dir"
    # "/." copies the full directory contents including dotfiles; do NOT
    # swallow errors — a failed copy must be surfaced, not reported as success.
    # SDK fetch is non-fatal: EFC install already succeeded, continue with
    # the remaining steps (systemd deploy / service start).
    if ! sudo cp -af "$sdk_dir"/. "$sdk_install_dir/"; then
        error "Failed to copy SDK files to ${sdk_install_dir}."
        warn  "Re-download and extract manually: ${SDK_URL_PREFIX}/${SDK_PKG}"
        warn  "Continuing with the remaining installation steps."
        return 0
    fi
    # Atomically update the 'latest' symlink to the newly fetched version
    # (-T treats the destination as a link name, not a directory to descend into).
    if ! sudo ln -sfnT "$sdk_install_dir" "$sdk_latest_link"; then
        warn "Failed to update ${sdk_latest_link} symlink; SDK is still available at ${sdk_install_dir}"
    fi
    info "SDK files placed at: ${sdk_install_dir}"
    info "  Latest link: ${sdk_latest_link} -> ${sdk_install_dir}"
    info "  Python SDK:  ${sdk_latest_link}/python/    (pip install ${sdk_latest_link}/python/)"
    info "  Go SDK:      ${sdk_latest_link}/go/"
    info "  Rust SDK:    ${sdk_latest_link}/rust/"
    info "  C headers:   ${sdk_latest_link}/C/include/"
    info "  Env setup:   source ${sdk_latest_link}/env.sh"
}

# ============================================================
#  Step 6: Deploy systemd unit
# ============================================================
deploy_systemd() {
    banner "[6/7] Deploying systemd service..."

    if [[ "$DRY_RUN" == "1" ]]; then
        info "[dry-run] Would configure systemd unit"
        return
    fi

    # Ensure socket directory exists
    sudo mkdir -p /var/run/kvcs
    sudo chmod 777 /var/run/kvcs

    # Ensure log directory exists
    sudo mkdir -p "$LOG_DIR"

    # RPM/DEB installs ship a correct unit file in /usr/lib/systemd/system/ via
    # postinstall script. Only tarball mode needs kvcs-ctl install-systemd.
    # Also remove any stale /etc/systemd/system/kvcs-efc.service left by previous
    # tarball deployments (it overrides the RPM-provided unit with wrong paths).
    if [[ "$PKG_TYPE" == "tarball" ]]; then
        if [[ -n "${KVCS_CTL:-}" && -x "$KVCS_CTL" ]]; then
            info "Running: $KVCS_CTL install-systemd"
            sudo "$KVCS_CTL" install-systemd || {
                warn "kvcs-ctl install-systemd had issues, but continuing..."
            }
        else
            warn "kvcs-ctl not found or not executable. Skipping systemd installation."
            warn "Run manually after fixing: kvcs-ctl install-systemd"
            return
        fi
    else
        # RPM/DEB: unit already installed by package postinstall
        # Clean stale override if exists (from older tarball deployments)
        if [[ -f /etc/systemd/system/kvcs-efc.service ]]; then
            info "Removing stale /etc/systemd/system/kvcs-efc.service (RPM unit takes precedence)"
            sudo rm -f /etc/systemd/system/kvcs-efc.service
        fi
        sudo systemctl daemon-reload
        sudo systemctl enable kvcs-efc 2>/dev/null || true
    fi

    info "Systemd service deployed and enabled."
}

# ============================================================
#  Step 7: Start service & self-check
# ============================================================
start_and_verify() {
    if [[ "$START_SERVICE" != "y" ]]; then
        banner "[7/7] Skipping service start (user opted out)."
        info "Start manually: kvcs-ctl start-efc"
        return
    fi

    banner "[7/7] Starting service and running self-check..."

    if [[ "$DRY_RUN" == "1" ]]; then
        info "[dry-run] Would start kvcs-efc service and verify."
        return
    fi

    if [[ -z "${KVCS_CTL:-}" || ! -x "${KVCS_CTL:-}" ]]; then
        warn "kvcs-ctl not available. Cannot start service."
        return
    fi

    # Start EFC
    info "Starting kvcs-efc..."
    sudo "$KVCS_CTL" start-efc || {
        warn "Service start reported issues. Checking status..."
    }

    # Self-check: wait and verify
    sleep 3

    local checks_passed=0
    local checks_total=3

    # Check 1: Process alive
    if pgrep -f kvcs_efc_server >/dev/null 2>&1; then
        info "  [✓] kvcs_efc_server process is running"
        checks_passed=$((checks_passed + 1))
    else
        # Also check systemd status
        if systemctl is-active --quiet kvcs-efc 2>/dev/null; then
            info "  [✓] kvcs-efc service is active"
            checks_passed=$((checks_passed + 1))
        else
            error "  [✗] kvcs_efc_server is NOT running"
            warn "      Check: systemctl status kvcs-efc"
            warn "      Logs:  journalctl -u kvcs-efc --no-pager -n 20"
        fi
    fi

    # Check 2: gRPC socket
    local socket_path="/var/run/kvcs/efc-grpc.sock"
    local socket_wait=0
    while [[ $socket_wait -lt 15 ]]; do
        if [[ -S "$socket_path" ]]; then
            break
        fi
        sleep 1
        socket_wait=$((socket_wait + 1))
    done

    if [[ -S "$socket_path" ]]; then
        info "  [✓] gRPC socket exists: ${socket_path}"
        checks_passed=$((checks_passed + 1))
    else
        warn "  [?] gRPC socket not found yet: ${socket_path}"
        warn "      Service may still be initializing. Check again in a few seconds."
    fi

    # Check 3: No FATAL in recent logs
    local log_file="${LOG_DIR}/kvcs-efc.log"
    if [[ -f "$log_file" ]]; then
        if grep -qi "FATAL" "$log_file" 2>/dev/null | tail -5; then
            error "  [✗] FATAL errors found in log: ${log_file}"
            grep -i "FATAL" "$log_file" | tail -3 | sed 's/^/      /'
        else
            info "  [✓] No FATAL errors in log"
            checks_passed=$((checks_passed + 1))
        fi
    else
        # Check journalctl instead
        if journalctl -u kvcs-efc --no-pager -n 50 2>/dev/null | grep -qi "FATAL"; then
            error "  [✗] FATAL errors found in journal"
        else
            info "  [✓] No FATAL errors in journal"
            checks_passed=$((checks_passed + 1))
        fi
    fi

    echo ""
    if [[ $checks_passed -eq $checks_total ]]; then
        info "Self-check: ${checks_passed}/${checks_total} passed. Service is healthy!"
    else
        warn "Self-check: ${checks_passed}/${checks_total} passed. Some checks need attention."
    fi
}

# ============================================================
#  Installation complete summary
# ============================================================
print_summary() {
    echo ""
    banner "============================================"
    banner "  KVCacheStore Installation Complete!"
    banner "============================================"
    echo ""
    info "Installed components:"
    echo "  EFC Server:    ${PKG_TYPE} (v${INSTALL_VERSION})"
    if [[ "$INSTALL_SDK" == "y" ]]; then
        echo "  SDK files:     /opt/kvcs-sdk/${INSTALL_VERSION}/  (not installed; e.g. pip install /opt/kvcs-sdk/latest/python/)"
    fi
    echo ""
    info "Configuration: ${EFC_CONFIG_PATH:-/etc/kvcs/efc.yaml}"
    info "Logs:          ${LOG_DIR}"
    echo ""
    info "Useful commands:"
    if [[ "$PKG_TYPE" == "tarball" ]]; then
        echo "  ${INSTALL_DIR}/kvcs-ctl status          # Check service status"
        echo "  ${INSTALL_DIR}/kvcs-ctl start-efc       # Start EFC"
        echo "  ${INSTALL_DIR}/kvcs-ctl stop-efc        # Stop EFC"
        echo "  ${INSTALL_DIR}/kvcs-ctl reset-efc       # Reset EFC (danger)"
    else
        echo "  kvcs-ctl status          # Check service status"
        echo "  kvcs-ctl start-efc       # Start EFC"
        echo "  kvcs-ctl stop-efc        # Stop EFC"
        echo "  kvcs-ctl reset-efc       # Reset EFC (danger)"
    fi
    echo ""
    if [[ "$INSTALL_SDK" == "y" ]]; then
        echo "  source /opt/kvcs-sdk/latest/env.sh   # Setup SDK environment"
    fi
    echo ""
}

# ============================================================
#  Main
# ============================================================
main() {
    detect_os
    detect_arch
    check_prerequisites
    collect_config
    download_packages
    install_efc
    generate_config
    install_sdk
    deploy_systemd
    start_and_verify
    print_summary
}

if [[ "${KVCS_INSTALLER_SOURCE_ONLY:-0}" != "1" ]]; then
    main
fi
