#!/bin/sh
# host-apply — render THIS device's view of the fleet from data/mesh-hosts.json.
#
# Unlike the other renderers (which emit a uniform artifact), host-apply detects
# which host it runs on and computes addresses from THAT device's vantage point:
#
#   ssh HostName for self→target =
#     target.public            if the target has a public IP (robust, always up)
#     target.lan               elif target has a LAN IP AND self can reach the LAN
#                              (self has a LAN IP, or self is the roaming laptop —
#                               the wg tunnel routes the LAN /24, and the
#                               smart-lan-router daemon makes it direct when home)
#     target.wg                else (mesh-only)
#
# It writes a single managed block (Host <name> <aliases> → HostName/User) to the
# invoking user's ~/.ssh/config, placed at the TOP so it wins first-match over
# any hand-maintained stanzas. Old names are kept as Host aliases (alias-first).
# Each stanza sets `CheckHostIP no` (host keys are stable, IPs drift — trust is
# keyed on the name, so a DHCP move doesn't trip verification) and
# `StrictHostKeyChecking accept-new` (TOFU within the private fleet, so
# non-interactive/BatchMode hops to a freshly-moved host still work).
#
# Self is identified by matching the box's hostname/short-name or any local IPv4
# (incl. the wg IP) against hosts[].{name,aliases,lan,wg}.
#
# Usage:
#   host-apply              # --ssh-print : print this device's ssh block (default)
#   host-apply --ssh-diff   # diff against current ~/.ssh/config
#   host-apply --ssh-apply  # splice/replace the managed block (backs up first)
#   host-apply --whoami     # just print which host this device resolves to
#
# Companion (run separately, needs root): `mesh-hosts-render --install` writes
# this device's /etc/hosts view (the .wg/.lan names). Together they cover a
# device's ssh + hosts views from the one source of truth.

set -eu

mode=ssh-print
case "${1:-}" in
    ""|--ssh-print) mode=ssh-print ;;
    --ssh-diff)     mode=ssh-diff ;;
    --ssh-apply)    mode=ssh-apply ;;
    --whoami)       mode=whoami ;;
    *) echo "host-apply: unknown arg '$1'" >&2; exit 1 ;;
esac

BEGIN='# >>> net-tools fleet (managed by host-apply) — do not edit by hand'
END='# <<< net-tools fleet'
SSH_CONFIG="$HOME/.ssh/config"

# --- locate data file (symlink-resolving walk) ---------------------------------
self_path=$0
while [ -L "$self_path" ]; do
    link=$(readlink "$self_path")
    case $link in /*) self_path=$link ;; *) self_path=$(dirname "$self_path")/$link ;; esac
done
root=$(cd "$(dirname "$self_path")" && pwd)
while [ "$root" != "/" ] && [ ! -f "$root/data/mesh-hosts.json" ]; do root=$(dirname "$root"); done
data_file="$root/data/mesh-hosts.json"
[ -f "$data_file" ] || { echo "host-apply: cannot locate data/mesh-hosts.json" >&2; exit 1; }
command -v jq >/dev/null || { echo "host-apply: jq not installed" >&2; exit 1; }

hide_homelan=$(jq -r '.dx.hide_homelan // false' "$data_file")

# Overlay: current LAN IPs discovered by the daemon (data/lan-state.json, a
# {name: ip} map) override the static `lan` seed, so ssh tracks DHCP drift.
overlay='{}'
state_file="$root/data/lan-state.json"
if [ -f "$state_file" ] && jq -e . "$state_file" >/dev/null 2>&1; then
    overlay=$(cat "$state_file")
fi

# --- identify self -------------------------------------------------------------
short=$(hostname 2>/dev/null | cut -d. -f1)
[ -n "$short" ] || short=$(uname -n | cut -d. -f1)
if command -v ip >/dev/null 2>&1; then
    local_ips=$(ip -o -4 addr show 2>/dev/null | awk '{print $4}' | cut -d/ -f1)
else
    local_ips=$(ifconfig 2>/dev/null | awk '/inet /{print $2}')
fi
ips_json=$(printf '%s\n' $local_ips | jq -R . | jq -s .)

self=$(jq -r --arg h "$short" --argjson ips "$ips_json" '
    [ .hosts[]
      | . as $x
      | select( ($x.name == $h)
                or ($x.aliases | index($h))
                or ($x.lan != null and ($ips | index($x.lan)))
                or ($ips | index($x.wg)) )
      | $x.name ] | first // empty
' "$data_file")

[ -n "$self" ] || { echo "host-apply: could not identify this host (short=$short, ips=$local_ips) in mesh-hosts.json" >&2; exit 1; }

if [ "$mode" = "whoami" ]; then
    echo "$self"
    exit 0
fi

# self_reaches_lan: a host with its own LAN IP, or the roaming laptop (tunnel
# routes 10.0.0.0/24; the daemon makes it direct when home).
reachlan=$(jq -r --arg s "$self" '
    .hosts[] | select(.name == $s)
    | ((.lan != null) or (.class == "laptop"))
' "$data_file")

# --- render this device's ssh block --------------------------------------------
render_block() {
    printf '%s\n' "$BEGIN"
    if [ "$hide_homelan" = "true" ]; then
        printf '# rendered for: %s   (dx/cloud-only; homelan hidden — set dx.hide_homelan=false to recover)\n' "$self"
    else
        printf '# rendered for: %s   (vantage: %s)\n' "$self" \
            "$( [ "$reachlan" = "true" ] && echo 'LAN-capable → prefer .lan' || echo 'mesh-only → prefer .wg' )"
    fi
    jq -r --arg s "$self" --argjson reachlan "$reachlan" --argjson ov "$overlay" --argjson hide "$hide_homelan" '
        .hosts[]
        | select(.name != $s)
        | select(.ssh_user != null)
        | select( if $hide then (.class == "cloud") else true end )
        | . as $h
        | (($ov[$h.name]) // $h.lan) as $lan
        | ( $h.public
            // (if $reachlan and $lan != null then $lan else null end)
            // $h.wg ) as $addr
        | "\nHost \(([$h.name] + $h.aliases) | join(" "))\n    HostName \($addr)\n    User \($h.ssh_user // "lilith")"
          + (if $h.ssh_identity then "\n    IdentityFile \($h.ssh_identity)" else "" end)
          + "\n    CheckHostIP no\n    StrictHostKeyChecking accept-new"
    ' "$data_file"
    printf '\n%s\n' "$END"
}

block=$(render_block)

if [ "$mode" = "ssh-print" ]; then
    printf '%s\n' "$block"
    exit 0
fi

# Strip any existing managed block, then prepend the fresh one (top = wins).
current=""
[ -f "$SSH_CONFIG" ] && current=$(cat "$SSH_CONFIG")
stripped=$(printf '%s\n' "$current" | awk -v b="$BEGIN" -v e="$END" '
    $0 == b { skip = 1 } skip != 1 { print } $0 == e { skip = 0 }')
new=$(printf '%s\n\n%s\n' "$block" "$stripped")

if [ "$mode" = "ssh-diff" ]; then
    if command -v diff >/dev/null 2>&1; then
        printf '%s\n' "$new" | diff -u "${SSH_CONFIG:-/dev/null}" - || true
    else
        printf '%s\n' "$new"
    fi
    exit 0
fi

# --ssh-apply
if [ -f "$SSH_CONFIG" ] && printf '%s\n' "$new" | cmp -s - "$SSH_CONFIG"; then
    echo "host-apply: $SSH_CONFIG already up to date for $self"
    exit 0
fi
mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh"
[ -f "$SSH_CONFIG" ] && cp "$SSH_CONFIG" "$SSH_CONFIG.netbak"
printf '%s\n' "$new" > "$SSH_CONFIG"
chmod 600 "$SSH_CONFIG"
echo "host-apply: wrote $self's fleet block to $SSH_CONFIG (backup: $SSH_CONFIG.netbak)"
