#!/bin/bash
#
# Jiejingyun Usage Collector - macOS installer
#
# Run this script from the employee's normal macOS account, not with sudo.
# It installs ActivityWatch for the current user, downloads the sync client,
# writes a protected config file, and creates per-user LaunchAgents.
#

set -Eeuo pipefail
IFS=$'\n\t'
umask 077

SERVER_URL="${SERVER_URL:-https://cc.jiejingyun.org}"
ACTIVITYWATCH_VERSION="${ACTIVITYWATCH_VERSION:-v0.14.0b3}"

SYNC_SHA256="a4a7c6eef8c93db80fad666c79fc2865e1040c63c19f5acbae56b778db190e72"
STORE_SHA256="a10ad39e455620d2fd845eff50126e1f6b20978286e581b67ffa3ba079a3909b"
AW_MAC_ARM64_SHA256="f4bcafc1368e8fda2f619b86db55925ad417538d2d39253cbec97121e8d6a1ab"
AW_MAC_X86_64_SHA256="8fe7e7cf2e0b6e6f02e55814d6a1e36a1b1896ab20218883b7cf76887dc7a7db"

PYTHON_VERSION="3.12.10"
PYTHON_PKG_URL="https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-macos11.pkg"

INSTALL_DIR="${HOME}/Library/Application Support/JiejingyunUsageCollector"
LOG_DIR="${HOME}/Library/Logs/JiejingyunUsageCollector"
LAUNCH_AGENT_DIR="${HOME}/Library/LaunchAgents"
USER_APPLICATIONS_DIR="${HOME}/Applications"

SYNC_FILE="${INSTALL_DIR}/usage-sync.py"
STORE_FILE="${INSTALL_DIR}/workforce_snapshot_store.py"
CONFIG_FILE="${INSTALL_DIR}/config.json"
RUNNER_FILE="${INSTALL_DIR}/run-sync.sh"

SYNC_LABEL="org.jiejingyun.usage-collector"
AW_LABEL="org.jiejingyun.activitywatch"
SYNC_PLIST="${LAUNCH_AGENT_DIR}/${SYNC_LABEL}.plist"
AW_PLIST="${LAUNCH_AGENT_DIR}/${AW_LABEL}.plist"

TEMP_DIR=""
MOUNT_POINT=""
DMG_ATTACHED=0

cleanup() {
    local exit_code=$?
    if [[ "${DMG_ATTACHED}" -eq 1 && -n "${MOUNT_POINT}" && -d "${MOUNT_POINT}" ]]; then
        /usr/bin/hdiutil detach "${MOUNT_POINT}" -quiet >/dev/null 2>&1 || true
    fi
    if [[ -n "${TEMP_DIR}" && -d "${TEMP_DIR}" ]]; then
        /bin/rm -rf "${TEMP_DIR}"
    fi
    return "${exit_code}"
}

on_error() {
    local exit_code=$?
    local line_number="${1:-unknown}"
    echo
    echo "安装失败：脚本第 ${line_number} 行执行出错，退出码 ${exit_code}。" >&2
    echo "请保留当前终端输出用于排查。" >&2
    exit "${exit_code}"
}

trap cleanup EXIT
trap 'on_error $LINENO' ERR

require_macos() {
    if [[ "$(/usr/bin/uname -s)" != "Darwin" ]]; then
        echo "此脚本只能在 macOS 上运行。" >&2
        exit 1
    fi

    if [[ "${EUID}" -eq 0 ]]; then
        echo "请不要使用 sudo 运行整个脚本。" >&2
        echo "请登录员工自己的 macOS 账户后执行；仅安装 Python 时脚本会单独请求管理员密码。" >&2
        exit 1
    fi

    local major_version
    major_version="$(/usr/bin/sw_vers -productVersion | /usr/bin/cut -d. -f1)"
    if [[ "${major_version}" -lt 12 ]]; then
        echo "当前 macOS 版本过低。此安装器要求 macOS 12 或更高版本。" >&2
        exit 1
    fi
}

read_required() {
    local prompt="$1"
    local value=""
    while [[ -z "${value//[[:space:]]/}" ]]; do
        IFS= read -r -p "${prompt}: " value
    done
    printf '%s' "${value}"
}

read_secret() {
    local prompt="$1"
    local value=""
    while [[ -z "${value}" ]]; do
        IFS= read -r -s -p "${prompt}: " value
        echo
    done
    printf '%s' "${value}"
}

download_file() {
    local url="$1"
    local destination="$2"

    /usr/bin/curl \
        --fail \
        --silent \
        --show-error \
        --location \
        --retry 2 \
        --connect-timeout 20 \
        "${url}" \
        --output "${destination}"
}

download_verified() {
    local url="$1"
    local destination="$2"
    local expected_sha256="$3"
    local actual_sha256

    download_file "${url}" "${destination}"
    actual_sha256="$(/usr/bin/shasum -a 256 "${destination}" | /usr/bin/awk '{print $1}')"

    if [[ "${actual_sha256}" != "${expected_sha256}" ]]; then
        echo "文件校验失败：${destination}" >&2
        echo "预期：${expected_sha256}" >&2
        echo "实际：${actual_sha256}" >&2
        exit 1
    fi
}

python_is_compatible() {
    local candidate="$1"
    [[ -x "${candidate}" ]] || return 1
    "${candidate}" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)' \
        >/dev/null 2>&1
}

find_python() {
    local candidate=""

    for candidate in \
        "/opt/homebrew/bin/python3" \
        "/usr/local/bin/python3" \
        "/usr/local/bin/python3.12" \
        "/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12"
    do
        if python_is_compatible "${candidate}"; then
            printf '%s' "${candidate}"
            return 0
        fi
    done

    candidate="$(command -v python3 2>/dev/null || true)"
    if [[ -n "${candidate}" ]] && python_is_compatible "${candidate}"; then
        printf '%s' "${candidate}"
        return 0
    fi

    return 1
}

install_python_if_needed() {
    local python_bin=""
    local python_pkg=""
    local signature_output=""

    if python_bin="$(find_python)"; then
        printf '%s' "${python_bin}"
        return 0
    fi

    echo "未检测到 Python 3.10+，正在下载 Python ${PYTHON_VERSION} 官方安装包..."
    python_pkg="${TEMP_DIR}/python-${PYTHON_VERSION}.pkg"
    download_file "${PYTHON_PKG_URL}" "${python_pkg}"

    signature_output="$(/usr/sbin/pkgutil --check-signature "${python_pkg}" 2>&1)"
    if ! printf '%s\n' "${signature_output}" | /usr/bin/grep -q "Python Software Foundation"; then
        echo "Python 安装包签名校验失败，拒绝继续安装。" >&2
        printf '%s\n' "${signature_output}" >&2
        exit 1
    fi

    echo "安装 Python 时 macOS 将请求管理员密码..."
    /usr/bin/sudo /usr/sbin/installer -pkg "${python_pkg}" -target /

    if ! python_bin="$(find_python)"; then
        echo "Python 已执行安装，但没有找到可用的 Python 3.10+。" >&2
        exit 1
    fi

    printf '%s' "${python_bin}"
}

find_activitywatch_app() {
    local candidate=""
    for candidate in \
        "${HOME}/Applications/ActivityWatch.app" \
        "/Applications/ActivityWatch.app"
    do
        if [[ -d "${candidate}" ]]; then
            printf '%s' "${candidate}"
            return 0
        fi
    done
    return 1
}

install_activitywatch_if_needed() {
    local existing_app=""
    local machine_arch=""
    local aw_arch=""
    local aw_url=""
    local aw_expected_sha256=""
    local dmg_file=""
    local source_app=""
    local installed_app="${USER_APPLICATIONS_DIR}/ActivityWatch.app"
    local gatekeeper_ok=0

    if existing_app="$(find_activitywatch_app)"; then
        printf '%s' "${existing_app}"
        return 0
    fi

    machine_arch="$(/usr/bin/uname -m)"
    case "${machine_arch}" in
        arm64)
            aw_arch="arm64"
            aw_expected_sha256="${AW_MAC_ARM64_SHA256}"
            ;;
        x86_64)
            aw_arch="x86_64"
            aw_expected_sha256="${AW_MAC_X86_64_SHA256}"
            ;;
        *)
            echo "不支持的 Mac CPU 架构：${machine_arch}" >&2
            exit 1
            ;;
    esac

    aw_url="https://github.com/ActivityWatch/activitywatch/releases/download/${ACTIVITYWATCH_VERSION}/activitywatch-${ACTIVITYWATCH_VERSION}-macos-${aw_arch}.dmg"
    dmg_file="${TEMP_DIR}/activitywatch-${ACTIVITYWATCH_VERSION}-${aw_arch}.dmg"
    MOUNT_POINT="${TEMP_DIR}/activitywatch-mount"

    echo "未检测到 ActivityWatch，正在下载 ${ACTIVITYWATCH_VERSION}（${aw_arch}）..."
    download_verified "${aw_url}" "${dmg_file}" "${aw_expected_sha256}"

    /bin/mkdir -p "${MOUNT_POINT}"
    /usr/bin/hdiutil attach \
        -nobrowse \
        -readonly \
        -mountpoint "${MOUNT_POINT}" \
        "${dmg_file}" \
        >/dev/null
    DMG_ATTACHED=1

    source_app="$(/usr/bin/find "${MOUNT_POINT}" -maxdepth 2 -type d -name "ActivityWatch.app" -print -quit)"
    if [[ -z "${source_app}" || ! -d "${source_app}" ]]; then
        echo "ActivityWatch 安装镜像中未找到 ActivityWatch.app。" >&2
        exit 1
    fi

    if ! /usr/bin/codesign --verify --deep --strict "${source_app}" >/dev/null 2>&1; then
        echo "ActivityWatch 代码签名校验失败，拒绝安装。" >&2
        exit 1
    fi

    if /usr/sbin/spctl --assess --type execute "${source_app}" >/dev/null 2>&1; then
        gatekeeper_ok=1
    else
        echo "警告：Gatekeeper 未通过自动评估，首次启动时可能需要在“隐私与安全性”中确认打开。" >&2
    fi

    /bin/mkdir -p "${USER_APPLICATIONS_DIR}"
    /bin/rm -rf "${installed_app}"
    /usr/bin/ditto "${source_app}" "${installed_app}"

    if ! /usr/bin/codesign --verify --deep --strict "${installed_app}" >/dev/null 2>&1; then
        echo "ActivityWatch 复制后的代码签名校验失败。" >&2
        exit 1
    fi

    if [[ "${gatekeeper_ok}" -eq 1 ]]; then
        /usr/bin/xattr -dr com.apple.quarantine "${installed_app}" >/dev/null 2>&1 || true
    fi

    printf '%s' "${installed_app}"
}

bootstrap_device_config() {
    local python_bin="$1"
    local device_key="$2"
    local bootstrap_json=""
    local bootstrap_values=""

    bootstrap_json="$(/usr/bin/curl --fail --silent --show-error --location \
        --header "Authorization: Bearer ${device_key}" \
        "${SERVER_URL%/}/api/v1/devices/bootstrap")" || {
        echo "无法使用 Device Token 获取设备配置。请确认 Token、网络和服务器地址后重试。" >&2
        return 1
    }

    bootstrap_values="$(JY_BOOTSTRAP_JSON="${bootstrap_json}" "${python_bin}" - <<'PY'
import json
import os

payload = json.loads(os.environ["JY_BOOTSTRAP_JSON"])
values = [str(payload.get(key, "")).strip() for key in ("device_id", "employee_id", "hmac_secret")]
if not all(values):
    raise SystemExit("服务器返回的设备配置不完整")
print("\t".join(values))
PY
)" || {
        echo "设备配置解析失败。" >&2
        return 1
    }

    printf '%s' "${bootstrap_values}"
}

write_config() {
    local python_bin="$1"
    local device_id="$2"
    local employee_id="$3"
    local device_key="$4"
    local hmac_secret="$5"

    JY_CONFIG_FILE="${CONFIG_FILE}" \
    JY_SERVER_URL="${SERVER_URL}" \
    JY_DEVICE_ID="${device_id}" \
    JY_EMPLOYEE_ID="${employee_id}" \
    JY_DEVICE_KEY="${device_key}" \
    JY_HMAC_SECRET="${hmac_secret}" \
    "${python_bin}" <<'PY'
import json
import os
from pathlib import Path

path = Path(os.environ["JY_CONFIG_FILE"])
data = {
    "server_url": os.environ["JY_SERVER_URL"],
    "device_id": os.environ["JY_DEVICE_ID"],
    "employee_id": os.environ["JY_EMPLOYEE_ID"],
    "device_key": os.environ["JY_DEVICE_KEY"],
    "hmac_secret": os.environ["JY_HMAC_SECRET"],
}
path.write_text(
    json.dumps(data, ensure_ascii=False, indent=2),
    encoding="utf-8",
)
PY

    /bin/chmod 600 "${CONFIG_FILE}"
}

write_runner() {
    local python_bin="$1"

    cat > "${RUNNER_FILE}" <<EOF
#!/bin/bash
set -eu
export NO_PROXY="127.0.0.1,localhost,\${NO_PROXY:-}"
export no_proxy="127.0.0.1,localhost,\${no_proxy:-}"
exec "${python_bin}" "${SYNC_FILE}" --config "${CONFIG_FILE}"
EOF

    /bin/chmod 700 "${RUNNER_FILE}"
}

write_launch_agents() {
    local python_bin="$1"
    local activitywatch_app="$2"

    JY_SYNC_PLIST="${SYNC_PLIST}" \
    JY_AW_PLIST="${AW_PLIST}" \
    JY_RUNNER_FILE="${RUNNER_FILE}" \
    JY_LOG_DIR="${LOG_DIR}" \
    JY_AW_APP="${activitywatch_app}" \
    JY_SYNC_LABEL="${SYNC_LABEL}" \
    JY_AW_LABEL="${AW_LABEL}" \
    "${python_bin}" <<'PY'
import os
import plistlib
from pathlib import Path

sync_plist = Path(os.environ["JY_SYNC_PLIST"])
aw_plist = Path(os.environ["JY_AW_PLIST"])
runner = os.environ["JY_RUNNER_FILE"]
log_dir = os.environ["JY_LOG_DIR"]
aw_app = os.environ["JY_AW_APP"]

sync_data = {
    "Label": os.environ["JY_SYNC_LABEL"],
    "ProgramArguments": ["/bin/bash", runner],
    "RunAtLoad": True,
    "StartInterval": 300,
    "ProcessType": "Background",
    "StandardOutPath": f"{log_dir}/sync.stdout.log",
    "StandardErrorPath": f"{log_dir}/sync.stderr.log",
}

aw_data = {
    "Label": os.environ["JY_AW_LABEL"],
    "ProgramArguments": ["/usr/bin/open", "-g", aw_app],
    "RunAtLoad": True,
    "LimitLoadToSessionType": "Aqua",
    "StandardOutPath": f"{log_dir}/activitywatch.stdout.log",
    "StandardErrorPath": f"{log_dir}/activitywatch.stderr.log",
}

for path, data in ((sync_plist, sync_data), (aw_plist, aw_data)):
    with path.open("wb") as fh:
        plistlib.dump(data, fh, fmt=plistlib.FMT_XML, sort_keys=True)
PY

    /bin/chmod 600 "${SYNC_PLIST}" "${AW_PLIST}"
    /usr/bin/plutil -lint "${SYNC_PLIST}" >/dev/null
    /usr/bin/plutil -lint "${AW_PLIST}" >/dev/null
}

reload_launch_agent() {
    local plist_path="$1"
    local label="$2"
    local domain="gui/${UID}"

    /bin/launchctl bootout "${domain}" "${plist_path}" >/dev/null 2>&1 || true
    /bin/launchctl bootstrap "${domain}" "${plist_path}"
    /bin/launchctl enable "${domain}/${label}" >/dev/null 2>&1 || true
    /bin/launchctl kickstart -k "${domain}/${label}"
}

wait_for_activitywatch() {
    local attempt=0
    for attempt in $(/usr/bin/seq 1 30); do
        if /usr/bin/curl \
            --fail \
            --silent \
            --show-error \
            --location \
            --noproxy "*" \
            "http://127.0.0.1:5600/api/0/info" \
            >/dev/null 2>&1
        then
            return 0
        fi
        /bin/sleep 1
    done
    return 1
}

main() {
    local device_id=""
    local employee_id=""
    local device_key=""
    local hmac_secret=""
    local python_bin=""
    local activitywatch_app=""
    local client_base=""

    require_macos

    TEMP_DIR="$(/usr/bin/mktemp -d "${TMPDIR:-/tmp}/jiejingyun-macos.XXXXXX")"

    echo "洁净云使用数据采集组件 - macOS 安装"
    echo "仅需输入 Device Token，设备与成员信息将由服务器自动绑定。"
    echo

    device_key="$(read_secret "设备 Token")"

    /bin/mkdir -p \
        "${INSTALL_DIR}" \
        "${LOG_DIR}" \
        "${LAUNCH_AGENT_DIR}" \
        "${USER_APPLICATIONS_DIR}"

    python_bin="$(install_python_if_needed)"
    echo "使用 Python：${python_bin}"

    bootstrap_values="$(bootstrap_device_config "${python_bin}" "${device_key}")" || exit 4
    IFS=$'\t' read -r device_id employee_id hmac_secret <<< "${bootstrap_values}"
    echo "已绑定设备：${device_id}（成员：${employee_id}）"

    client_base="${SERVER_URL%/}/admin/client"
    echo "下载同步组件..."
    download_verified "${client_base}/usage-sync.py" "${SYNC_FILE}" "${SYNC_SHA256}"
    download_verified "${client_base}/workforce_snapshot_store.py" "${STORE_FILE}" "${STORE_SHA256}"
    /bin/chmod 600 "${SYNC_FILE}" "${STORE_FILE}"

    write_config \
        "${python_bin}" \
        "${device_id}" \
        "${employee_id}" \
        "${device_key}" \
        "${hmac_secret}"

    unset device_key
    unset hmac_secret
    unset JY_DEVICE_KEY
    unset JY_HMAC_SECRET

    write_runner "${python_bin}"

    activitywatch_app="$(install_activitywatch_if_needed)"
    echo "ActivityWatch：${activitywatch_app}"

    write_launch_agents "${python_bin}" "${activitywatch_app}"

    reload_launch_agent "${AW_PLIST}" "${AW_LABEL}"

    echo "正在等待 ActivityWatch 本地服务启动..."
    if wait_for_activitywatch; then
        echo "ActivityWatch 本地服务已启动。"
    else
        echo "警告：30 秒内未检测到 ActivityWatch 本地服务。" >&2
        echo "请检查 macOS 是否弹出了首次启动或隐私权限提示。" >&2
    fi

    reload_launch_agent "${SYNC_PLIST}" "${SYNC_LABEL}"

    echo
    echo "安装完成。"
    echo "ActivityWatch 已设置为登录后自动启动。"
    echo "同步任务已设置为每 5 分钟运行一次。"
    echo "配置目录：${INSTALL_DIR}"
    echo "日志目录：${LOG_DIR}"
    echo
    echo "重要：请在 macOS“系统设置 → 隐私与安全性 → 辅助功能”中允许 ActivityWatch。"
    echo "首次启动如出现“控制 System Events”提示，请选择允许。"

    /usr/bin/open \
        "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" \
        >/dev/null 2>&1 || true

    echo
    IFS= read -r -p "按 Enter 键结束安装；之后可以关闭终端窗口。"
}

main "$@"
