#!/bin/bash
# 离线资产收集与安装。交换点固化为套件 archives/ 目录（目录契约见 archives/README.md）：
#   collect            → archives/gush-offline-bundle-<日期>.tar.gz + MANIFEST.txt（内容清单+SHA256）
#   install [bundle]   → 缺省取 archives/ 最新离线包，先按 MANIFEST 校验 SHA256 再解包落位
# 打包内容：Go 工具链 tarball + 静态工具二进制 + 模块缓存（含升级目标版本预拉）
#          + govulncheck 漏洞库镜像（如已预置）+ Claude 智能资产（项目级/用户级分目录）。
# 大文件默认不入库（archives/.gitignore），MANIFEST 哈希供跨介质审计对账。
set -euo pipefail
source "$(cd "$(dirname "$0")/.." && pwd)/harness.env"

mode="${1:?用法: 01-offline-bundle.sh collect | install [bundle路径]}"
mkdir -p "${GUSH_ARCHIVES_DIR}"

case "${mode}" in
collect)
    day="$(date +%Y%m%d)"
    bundle="${GUSH_ARCHIVES_DIR}/gush-offline-bundle-${day}.tar.gz"
    mkdir -p "${GUSH_OFFLINE_DIR}/tarballs" "${GUSH_OFFLINE_DIR}/claude-assets/project" \
        "${GUSH_OFFLINE_DIR}/claude-assets/user" "${GUSH_GOMODCACHE}"
    gush_use_go "${GUSH_GO_TARGET_DEFAULT}"
    # 1. Go 工具链 tarball 与静态工具二进制由 00-install-tools.sh 产出，已留存于工作区
    # 2. 模块缓存预热：既拉当前 go.mod 的全量依赖，也按 repos.list 预拉升级目标版本
    #    （在线/离线共用 GUSH_GOMODCACHE，见 harness.env，预热即所用）
    if [ -d "${GUSH_REPOS_DIR}" ]; then
        grep -vE '^[[:space:]]*(#|$)' "${GUSH_REPOS_LIST}" | while IFS='|' read -r name _url _branch _cur _tgt libs _owner; do
            d="$(gush_use_repo_path "${name}")"
            [ -d "${d}" ] || continue
            # 多模块仓：对每个叶子模块分别预热模块缓存
            warm_modules=()
            if [ -f "${d}/go.mod" ]; then
                warm_modules=(".")
            else
                mapfile -t warm_modules < <(gush_list_modules "${name}")
            fi
            [ "${#warm_modules[@]}" -gt 0 ] || continue
            for wm in "${warm_modules[@]}"; do
                wmpath="${d}"
                wmlabel="${name}"
                [ "${wm}" = "." ] || { wmpath="${d}/${wm}"; wmlabel="${name}/${wm}"; }
                [ -f "${wmpath}/go.mod" ] || continue
                echo "[warm] ${wmlabel}"
                (cd "${wmpath}" && go mod download all) || echo "[warn] ${wmlabel} 全量预热失败"
                if [ "${libs}" != "-" ] && [ -n "${libs}" ]; then
                    IFS=',' read -ra mods <<<"${libs}"
                    for m in "${mods[@]}"; do
                        # 阶梯库逐档预拉（module@v1>v2 -> module@v1 与 module@v2）
                        mod="${m%%@*}"
                        IFS='>' read -ra rungs <<<"${m#*@}"
                        for r in "${rungs[@]}"; do
                            (cd "${wmpath}" && go mod download "${mod}@${r}") || echo "[warn] ${wmlabel} 目标版本 ${mod}@${r} 预热失败"
                        done
                    done
                fi
            done
        done
    else
        echo "[warn] ${GUSH_REPOS_DIR} 不存在，未预热模块缓存（先跑 10-clone-repos.sh 再 collect）"
    fi
    # 3. govulncheck 漏洞库镜像：须按官方 x/vulndb 镜像方法预置到 ${GUSH_VULNDB_DIR}；
    #    缺失时如实告警——离线机上最终跳 GU5 将 fail-loud（绝不出假绿）
    if [ -d "${GUSH_VULNDB_DIR}" ]; then
        echo "[pack] vulndb 镜像 ${GUSH_VULNDB_DIR}"
    else
        echo "[warn] 未预置 ${GUSH_VULNDB_DIR}，离线机 GU5 漏洞门禁将无法判定（fail-loud）"
    fi
    # 4. Claude 智能资产：项目级与用户级分目录收集，避免同名合并覆盖
    for src in "${GUSH_PROJECT_ROOT}/.claude/skills" "${GUSH_PROJECT_ROOT}/.claude/commands"; do
        [ -e "${src}" ] && rsync -a "${src}" "${GUSH_OFFLINE_DIR}/claude-assets/project/"
    done
    for src in "${HOME}/.claude/skills" "${HOME}/.claude/plugins" "${HOME}/.claude/agents"; do
        [ -e "${src}" ] && rsync -a --exclude 'cache/temp_*' "${src}" "${GUSH_OFFLINE_DIR}/claude-assets/user/"
    done
    tar -C "${GUSH_WORK_ROOT}" -czf "${bundle}" offline tools toolchains
    sha="$(sha256sum "${bundle}" | awk '{print $1}')"
    {
        echo "bundle: $(basename "${bundle}")"
        echo "sha256: ${sha}"
        echo "size: $(du -h "${bundle}" | cut -f1)"
        echo "date: ${day}"
        echo "toolchains: $(ls "${GUSH_TOOLCHAINS_DIR}" 2>/dev/null | tr '\n' ' ')"
        echo "tools: $(ls "${GUSH_TOOLS_DIR}/bin" 2>/dev/null | tr '\n' ' ')"
        echo "vulndb: $([ -d "${GUSH_VULNDB_DIR}" ] && echo included || echo absent)"
        echo "modcache_size: $(du -sh "${GUSH_GOMODCACHE}" 2>/dev/null | cut -f1)"
    } > "${GUSH_ARCHIVES_DIR}/MANIFEST.txt"
    echo "[done] 离线包: ${bundle}"
    echo "[done] 清单: ${GUSH_ARCHIVES_DIR}/MANIFEST.txt（整个 archives/ 目录拷贝至离线机后执行 install）"
    ;;
install)
    src="${2:-}"
    if [ -z "${src}" ]; then
        src="$(ls -1 "${GUSH_ARCHIVES_DIR}"/gush-offline-bundle-*.tar.gz 2>/dev/null | sort | tail -1 || true)"
        [ -n "${src}" ] || { echo "[err] ${GUSH_ARCHIVES_DIR} 下无离线包，请显式传入 bundle 路径" >&2; exit 1; }
        echo "[pick] 取 archives/ 最新离线包: $(basename "${src}")"
    fi
    # MANIFEST 完整性校验：有匹配记录必须过哈希，无记录如实警告后继续
    if [ -f "${GUSH_ARCHIVES_DIR}/MANIFEST.txt" ] && grep -q "bundle: $(basename "${src}")" "${GUSH_ARCHIVES_DIR}/MANIFEST.txt"; then
        want="$(awk '$1 == "sha256:" { print $2; exit }' "${GUSH_ARCHIVES_DIR}/MANIFEST.txt")"
        got="$(sha256sum "${src}" | awk '{print $1}')"
        if [ "${want}" != "${got}" ]; then
            echo "[err] $(basename "${src}") SHA256 与 MANIFEST 不符，拒绝安装（介质损坏或被篡改）" >&2
            exit 1
        fi
        echo "[ok] SHA256 校验通过"
    else
        echo "[warn] MANIFEST 无该包记录，跳过完整性校验"
    fi
    mkdir -p "${GUSH_WORK_ROOT}" "${HOME}/.claude"
    tar -C "${GUSH_WORK_ROOT}" -xzf "${src}"
    # 用户级资产恢复到 ~/.claude；项目级资产（skills/commands，含 review-3persona）
    # 需按目标项目手工拷入其 .claude/ 目录，此处只提示不越权写入
    for a in skills plugins agents; do
        [ -d "${GUSH_OFFLINE_DIR}/claude-assets/user/${a}" ] || continue
        rsync -a "${GUSH_OFFLINE_DIR}/claude-assets/user/${a}" "${HOME}/.claude/"
    done
    if [ -d "${GUSH_OFFLINE_DIR}/claude-assets/project" ]; then
        echo "[note] 项目级资产在 ${GUSH_OFFLINE_DIR}/claude-assets/project/（skills、commands），按需拷入目标项目 .claude/"
    fi
    echo "[done] 离线安装完成。后续每个 shell 先 export GUSH_OFFLINE_MODE=1 再 source harness.env"
    ;;
*)
    echo "[err] 未知模式: ${mode}" >&2
    exit 1
    ;;
esac
