#!/bin/bash

# Pairing token — the out-of-band string two LibrePortal instances exchange
# so each can talk to the other over SSH with a forced-command. Format:
#
#   lp-peer|v1|<name>|<user>|<host>|<port>|<pubkey-b64>|<fingerprint>
#
# Pipe-delimited because SSH fingerprints (SHA256:base64) and base64 itself
# include `:` and `=`, so colon-splitting eats fields. Pipe is safe: base64
# alphabet is [A-Za-z0-9+/=], fingerprints are SHA256:[A-Za-z0-9+/=], names
# and hostnames don't contain pipes.
#
# - name        Friendly label the OTHER side suggests for itself ("homelab").
#               The accepting side may override at peer-add time.
# - user        Manager username on the originating host (the SSH login).
# - host:port   How to reach the originating host. port defaults to 22.
# - pubkey      base64 of "ssh-ed25519 AAAA… comment" — the originator's
#               peer pubkey (peerKeyPublic).
# - fingerprint SHA256:… of the pubkey, for the accepting user to verify.

peerPairingToken()
{
    peerKeyEnsure || return 1

    local name="${CFG_INSTALL_NAME:-$(hostname)}"
    local user
    user="$(whoami)"
    local host="${CFG_PUBLIC_HOSTNAME:-${CFG_INSTALL_NAME:-$(hostname)}}"
    local port="${CFG_SSH_PORT:-22}"

    local pub fp pub_b64
    pub=$(peerKeyPublic) || { isError "Could not read peer pubkey"; return 1; }
    fp=$(peerKeyFingerprint)
    # base64 -w0 keeps the encoded pubkey on a single token field. The pubkey
    # itself already contains spaces — we MUST encode it.
    pub_b64=$(printf '%s' "$pub" | base64 -w0)

    printf 'lp-peer|v1|%s|%s|%s|%s|%s|%s\n' \
        "$name" "$user" "$host" "$port" "$pub_b64" "$fp"
}

# Parse a token. Echoes a single-line JSON object on success, JSON {"error":…}
# on failure (return 1).
peerPairingParse()
{
    local token="$1"
    if [[ -z "$token" || "$token" != lp-peer\|* ]]; then
        echo '{"error":"not-a-token"}'
        return 1
    fi

    # Pipe-split. Easy now: every field is just "${arr[N]}".
    local IFS='|'
    # shellcheck disable=SC2206
    local parts=( $token )
    IFS=$' \t\n'

    if [[ ${#parts[@]} -lt 8 || "${parts[0]}" != "lp-peer" ]]; then
        echo '{"error":"malformed"}'
        return 1
    fi
    local version="${parts[1]}"
    if [[ "$version" != "v1" ]]; then
        echo "{\"error\":\"unsupported-version\",\"version\":\"$version\"}"
        return 1
    fi
    local name="${parts[2]}"
    local user="${parts[3]}"
    local host="${parts[4]}"
    local port="${parts[5]}"
    local pubkey_b64="${parts[6]}"
    local fp="${parts[7]}"

    local pubkey
    pubkey=$(printf '%s' "$pubkey_b64" | base64 -d 2>/dev/null)
    if [[ -z "$pubkey" ]] || ! printf '%s\n' "$pubkey" | ssh-keygen -l -f - >/dev/null 2>&1; then
        echo '{"error":"invalid-pubkey"}'
        return 1
    fi

    # Re-derive fingerprint from the actual key and confirm it matches what
    # the token advertised — catches truncated/edited tokens.
    local actual_fp
    actual_fp=$(printf '%s' "$pubkey" | ssh-keygen -l -f - 2>/dev/null | awk '{print $2}')
    if [[ -n "$fp" && "$actual_fp" != "$fp" ]]; then
        echo "{\"error\":\"fingerprint-mismatch\",\"advertised\":\"$fp\",\"actual\":\"$actual_fp\"}"
        return 1
    fi

    local pub_esc="${pubkey//\\/\\\\}"; pub_esc="${pub_esc//\"/\\\"}"
    printf '{"version":"v1","name":"%s","user":"%s","host":"%s","port":%s,"pubkey":"%s","fingerprint":"%s"}\n' \
        "$name" "$user" "$host" "$port" "$pub_esc" "$actual_fp"
}

# Pull a "key":"value" string field out of a flat JSON object. No jq.
_peerPairingJsonStr()
{
    printf '%s' "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | cut -d'"' -f4
}
# Pull a "key":N numeric field out of a flat JSON object.
_peerPairingJsonNum()
{
    printf '%s' "$1" | grep -o "\"$2\":[0-9]*" | head -1 | cut -d':' -f2
}

# Accept a pairing token: validate, install peer-shell, append authorized_keys
# entry with forced-command, create peers row. The local label can override
# the token's suggested name (handy when there's a collision).
peerPairingAccept()
{
    local token="$1"
    local override_name="$2"

    local parsed; parsed=$(peerPairingParse "$token")
    if [[ "$parsed" == *'"error"'* ]]; then
        isError "Token rejected: $parsed"
        return 1
    fi

    local name pubkey host port user
    name=$(_peerPairingJsonStr "$parsed" name)
    pubkey=$(_peerPairingJsonStr "$parsed" pubkey)
    host=$(_peerPairingJsonStr "$parsed" host)
    port=$(_peerPairingJsonNum "$parsed" port)
    user=$(_peerPairingJsonStr "$parsed" user)

    [[ -n "$override_name" ]] && name="$override_name"

    local nv; nv=$(peerValidateName "$name")
    if [[ "$nv" != "ok" ]]; then
        isError "Invalid peer name '$name': $nv"
        return 1
    fi

    # Install peer-shell and append the authorized_keys entry (this lets the
    # remote peer SSH IN to us). Adding the peer to OUR DB lets us SSH OUT to
    # them — symmetric pairing means the user runs accept on both sides.
    peerInstallShell || return 1

    local akf="${HOME}/.ssh/authorized_keys"
    mkdir -p "${HOME}/.ssh"
    chmod 700 "${HOME}/.ssh"
    touch "$akf"
    chmod 600 "$akf"

    # If the key body is already in authorized_keys (under any options),
    # don't double-add — but DO refresh the comment to flag it as a peer.
    local key_body; key_body=$(awk '{print $2}' <<< "$pubkey")
    if grep -qF "$key_body" "$akf"; then
        isNotice "Pubkey already in authorized_keys — not duplicating"
    else
        local shell_path; shell_path="${HOME}/.local/bin/peer-shell"
        local opts="command=\"$shell_path $name\",no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-user-rc"
        printf '%s %s peer:%s\n' "$opts" "$pubkey" "$name" >> "$akf"
        isSuccessful "Authorized peer '$name' to call peer-shell"
    fi

    # Now record the peer locally so we can SSH OUT to them.
    peerAdd "$name" direct-ssh-direct \
        "host=$host" "port=$port" "user=$user" "fingerprint=$(_peerPairingJsonStr "$parsed" fingerprint)" \
        || return 1
}
