#!/usr/bin/env bash
# install-twin — after-build shim.
#
# DESIGN (2026-04-20): ~/.local/bin/twin is a SYMLINK to the cargo
# release binary. One on-disk file, one CDHash, one Keychain ACL
# entry, one firewall rule. Previously we copied + re-codesigned
# ad-hoc, which minted a fresh CDHash on every install and caused
# repeated Keychain password prompts (the ACL is CDHash-keyed).
#
# With the symlink, launchd (which uses the absolute target path) and
# user shells (which resolve via PATH) both exec the same binary. A
# rebuild changes the CDHash once; approve once per rebuild, done.
#
# Firewall rules stay path-keyed via socketfilterfw — we add the
# symlink path AND the real target path so inbound TCP works whether
# the process is invoked via PATH or by launchd's absolute path.
#
# When we get a paid Apple Developer ID, sign the release binary with
# `-s "Developer ID Application: ..."` in a post-build step; the
# stable signature means zero Keychain prompts on rebuild.
#
# Usage:
#   cargo build --release --bin twin
#   bash scripts/install-twin
#
# Idempotent: safe to run repeatedly.

set -eu

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WORKSPACE="${ARA_PROTOCOL_ROOT:-/Users/ayo/Desktop/Project/ara-protocol}"
SRC_BIN="${WORKSPACE}/target/release/twin"
DST_BIN="${HOME}/.local/bin/twin"
FW="/usr/libexec/ApplicationFirewall/socketfilterfw"

if [ ! -x "$SRC_BIN" ]; then
    echo "error: ${SRC_BIN} missing or not executable" >&2
    echo "  run \`cargo build --release --bin twin\` in ${WORKSPACE} first" >&2
    exit 2
fi

mkdir -p "$(dirname "$DST_BIN")"

# Replace any existing file/symlink with a symlink to the cargo target.
# `ln -sfn` is the correct idiom: -f forces replace, -n treats an
# existing symlink as a file (prevents creating DST/link inside a
# pointed-to dir if DST happens to be a symlink to a dir).
ln -sfn "$SRC_BIN" "$DST_BIN"
echo "installed symlink: ${DST_BIN} -> ${SRC_BIN}"

# No re-codesigning. The cargo release binary is linker-signed
# (ad-hoc) already. Re-signing would mint a new CDHash and re-trigger
# the Keychain prompt — which is exactly what we're avoiding.

# Firewall: make twin an accepted app, allow inbound connections.
# socketfilterfw needs sudo in general, but `--add` on a user-path
# binary often doesn't prompt. If the firewall is off entirely, these
# are no-ops. Errors here are non-fatal for dev.
if [ -x "$FW" ]; then
    # Is the firewall on at all?
    FW_STATE=$("$FW" --getglobalstate 2>/dev/null | head -1 || echo "unknown")
    echo "macOS firewall: ${FW_STATE}"

    # Ensure twin is registered. --add is idempotent (returns 0 even
    # if already present). --unblockapp explicitly allows inbound.
    if ! sudo -n true 2>/dev/null; then
        echo "note: firewall add/unblock needs sudo."
        echo "      running without sudo; the FIRST time after a fresh install"
        echo "      you may still see the macOS approval prompt once — subsequent"
        echo "      rebuilds will reuse the existing rule."
    fi

    # Add firewall rules for BOTH the symlink path (what shell resolves
    # via PATH) and the real target path (what launchd plists exec
    # directly). socketfilterfw is path-keyed, so both need rules.
    for P in "$DST_BIN" "$SRC_BIN"; do
        if ! "$FW" --add "$P" 2>/dev/null; then
            sudo "$FW" --add "$P" 2>/dev/null || true
        fi
        if ! "$FW" --unblockapp "$P" 2>/dev/null; then
            sudo "$FW" --unblockapp "$P" 2>/dev/null || true
        fi
    done

    echo "firewall: twin allow-rules added + unblocked (symlink + target)"
else
    echo "skip: firewall not present (socketfilterfw missing)"
fi

echo ""
echo "ready: ${DST_BIN}"
