#!/usr/bin/env bash
# se_client.sh — Shared library for sysd-engine mesh client tools
# Sourced by: askmail, cmdmail, getmail, logmail, paymail, pollmail, unmail
set -euo pipefail
shopt -s inherit_errexit 2>/dev/null || true

# ── Configuration ──────────────────────────────────────────────────────────
readonly SE_CLIENT_VERSION="4.0.0-hardened"
readonly NODE_ID="${NODE_ID:-$(cat /run/se/.nodeconf 2>/dev/null || hostname -s)}"
readonly ENGINE_IP="${ENGINE_IP:-172.28.50.1}"  # forge tunnel (tool→Engine ONLY, not wg7)
readonly ENGINE_PORT="${ENGINE_PORT:-8301}"
readonly ENGINE_URL="http://${ENGINE_IP}:${ENGINE_PORT}"
readonly HMAC_KEY_FILE="${HMAC_KEY_FILE:-/dev/shm/engine_hmac.key}"
readonly HMAC_KEY="${HMAC_KEY:-$(cat "${HMAC_KEY_FILE}" 2>/dev/null | head -c 64 || printf '%s' 'INVALID')}"
# Vault key kept separate — for tool decryption ONLY, never HMAC signing
readonly VAULT_KEY_FILE="${VAULT_KEY_FILE:-/dev/shm/vault.key}"
readonly VAULT_KEY="${VAULT_KEY:-$(cat "${VAULT_KEY_FILE}" 2>/dev/null | head -c 64 || printf '%s' 'INVALID')}"
readonly SE_LOG_TAG="se[${NODE_ID}]"
readonly SE_CURL_TIMEOUT="${SE_CURL_TIMEOUT:-15}"
readonly SE_CURL_CONNECT_TIMEOUT="${SE_CURL_CONNECT_TIMEOUT:-5}"
readonly SE_MAX_RETRIES="${SE_MAX_RETRIES:-3}"

# ── Input Validation ───────────────────────────────────────────────────────
[[ "${NODE_ID}" =~ ^[a-zA-Z0-9_-]+$ ]] || { echo "Invalid NODE_ID" >&2; exit 1; }
[[ "${ENGINE_IP}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "Invalid ENGINE_IP" >&2; exit 1; }
[[ "${ENGINE_PORT}" =~ ^[0-9]+$ ]] && (( ENGINE_PORT > 0 && ENGINE_PORT <= 65535 )) || { echo "Invalid ENGINE_PORT" >&2; exit 1; }

# ── Logging ────────────────────────────────────────────────────────────────
se_log() {
    local lvl="${1:-info}" msg="${2:-}"
    local priority
    case "${lvl}" in
        err|error) priority="user.err" ;;
        alert|crit) priority="user.crit" ;;
        warn|warning) priority="user.warning" ;;
        *) priority="user.info" ;;
    esac
    logger -t "${SE_LOG_TAG}" -p "${priority}" "${msg}" 2>/dev/null || true
}

# ── Session Signing Code Resolution ────────────────────────────────────────
_se_resolve_signing_code() {
    # Read signing_code_enc from active session, decrypt with vault key.
    # Returns plaintext signing code or empty string on failure (graceful).
    local sid="${HELLO_SESSION_ID:-}"
    local session_root="${SESSION_ROOT:-/opt/binary/session}"
    local session_file="${session_root}/active/${sid}.json"

    # No active session — return empty (graceful fallback)
    if [[ -z "${sid}" || ! -f "${session_file}" ]]; then
        return 0
    fi

    local enc_value
    enc_value="$(python3 -c "
import json, sys
try:
    d = json.load(open(sys.argv[1]))
    print(d.get('signing_code_enc', ''))
except: pass
" "${session_file}" 2>/dev/null)" || true

    if [[ -z "${enc_value}" ]]; then
        return 0
    fi

    # Decrypt with vault key
    local decrypted
    decrypted="$(printf '%s' "${enc_value}" | \
        openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 \
        -pass "pass:${VAULT_KEY}" -a -A 2>/dev/null)" || true

    if [[ -n "${decrypted}" ]]; then
        printf '%s' "${decrypted}"
    fi
}

# ── HMAC Signing ───────────────────────────────────────────────────────────
se_hmac_sign() {
    local method="${1:-}" path="${2:-}" body="${3:-}"
    if [[ -z "${method}" || -z "${path}" ]]; then
        se_log "err" "hmac_sign: missing method or path"
        return 1
    fi
    if [[ "${HMAC_KEY}" == "INVALID" || "${#HMAC_KEY}" -lt 32 ]]; then
        se_log "err" "hmac_sign: invalid HMAC key (len=${#HMAC_KEY})"
        return 1
    fi

    local ts nonce data sig
    ts="$(date -u +%s)"
    nonce="$(openssl rand -hex 16)"
    # CRY-02 canonical: HMAC-SHA256( ts.nonce.METHOD.path.body ) — dots, with replay protection
    data="${ts}.${nonce}.${method}.${path}.${body}"
    sig="$(printf '%s' "${data}" | openssl dgst -sha256 -hmac "${HMAC_KEY}" | awk '{print $NF}')"
    printf '%s\n%s\n%s\n' "${ts}" "${nonce}" "${sig}"
}

# ── HTTP GET ───────────────────────────────────────────────────────────────
se_curl_get() {
    local path="${1:-}"
    if [[ -z "${path}" ]]; then
        se_log "err" "curl_get: empty path"
        return 1
    fi
    local ts nonce sig
    read -r ts nonce sig < <(se_hmac_sign "GET" "${path}" "") || return 1
    curl -sS --fail-with-body \
        --connect-timeout "${SE_CURL_CONNECT_TIMEOUT}" \
        -m "${SE_CURL_TIMEOUT}" \
        "${ENGINE_URL}${path}" \
        -H "x-engine-ts: ${ts}" \
        -H "x-engine-nonce: ${nonce}" \
        -H "x-engine-sig: ${sig}" \
        -H "x-engine-node: ${NODE_ID}" \
        2>/dev/null || { se_log "err" "curl_get: ${path} failed"; return 1; }
}

# ── HTTP POST ──────────────────────────────────────────────────────────────
se_curl_post() {
    local path="${1:-}" body="${2:-}"
    if [[ -z "${path}" ]]; then
        se_log "err" "curl_post: empty path"
        return 1
    fi
    local ts nonce sig
    read -r ts nonce sig < <(se_hmac_sign "POST" "${path}" "${body}") || return 1
    curl -sS --fail-with-body \
        --connect-timeout "${SE_CURL_CONNECT_TIMEOUT}" \
        -m "${SE_CURL_TIMEOUT}" \
        -X POST "${ENGINE_URL}${path}" \
        -H "Content-Type: application/json" \
        -H "x-engine-ts: ${ts}" \
        -H "x-engine-nonce: ${nonce}" \
        -H "x-engine-sig: ${sig}" \
        -H "x-engine-node: ${NODE_ID}" \
        -d "${body}" \
        2>/dev/null || { se_log "err" "curl_post: ${path} failed"; return 1; }
}

# ── HTTP POST with Retry ───────────────────────────────────────────────────
se_curl_post_retry() {
    local path="${1:-}" body="${2:-}"
    local delay=1 retries=0 resp=''
    if [[ -z "${path}" ]]; then
        se_log "err" "curl_post_retry: empty path"
        return 1
    fi
    while (( retries < SE_MAX_RETRIES )); do
        resp="$(se_curl_post "${path}" "${body}" 2>/dev/null)" && { printf '%s' "${resp}"; return 0; }
        ((retries++))
        se_log "warn" "curl_post_retry: ${path} attempt ${retries}/${SE_MAX_RETRIES} failed, delay=${delay}s"
        sleep "${delay}"
        delay=$(( delay * 2 ))
    done
    se_log "err" "curl_post_retry: ${path} all ${SE_MAX_RETRIES} attempts failed"
    return 1
}

# ── Cleanup on exit ────────────────────────────────────────────────────────
se_cleanup() {
    :
}
trap se_cleanup EXIT

# ── Version export ─────────────────────────────────────────────────────────
export SE_CLIENT_VERSION NODE_ID ENGINE_URL VAULT_KEY SE_LOG_TAG
