#!/usr/bin/env bash
# =============================================================================
# /opt/binary/tools/common.sh -- Hardened Helper Library
# =============================================================================
# Shared execution context for mesh tools.
# No global side effects beyond pipefail and exported path constants.

[[ -n "${_COMMON_LOADED:-}" ]] && return 0
readonly _COMMON_LOADED=1

set -o pipefail

export OPT_BASE="${OPT_BASE:-/opt/binary}"
export TOOLS_ROOT="${TOOLS_ROOT:-${OPT_BASE}/tools}"
export TOOLS_DIR="${TOOLS_DIR:-${TOOLS_ROOT}}"
export DATA_DIR="${DATA_DIR:-${OPT_BASE}/data}"
export LOG_DIR="${LOG_DIR:-${OPT_BASE}/logs}"
export CONF_DIR="${CONF_DIR:-/etc/binary}"
export RUN_DIR="${RUN_DIR:-/run/binary}"
export NOTIFY_DIR="${NOTIFY_DIR:-${DATA_DIR}/notifications}"
export BASELINE_DB="${BASELINE_DB:-${DATA_DIR}/baseline.db}"
export STRIKE_FILE="${STRIKE_FILE:-${DATA_DIR}/strikes.txt}"
export HONEYPOT_LOG="${HONEYPOT_LOG:-${LOG_DIR}/veil/honeypot.log}"

export AUDIT_DIR="${AUDIT_DIR:-${LOG_DIR}/audit}"
export AUDIT_LOG="${AUDIT_LOG:-${AUDIT_DIR}/audit.log}"
export AUDIT_HASH_CHAIN="${AUDIT_HASH_CHAIN:-${AUDIT_DIR}/audit.chain}"

export C_NC='\033[0m'
export C_BOLD='\033[1m'
export C_RED='\033[0;31m'
export C_GREEN='\033[0;32m'
export C_YELLOW='\033[1;33m'
export C_CYAN='\033[0;36m'
export C_GRAY='\033[0;90m'
export C_BLUE='\033[0;34m'

export NC="$C_NC"
export RED="$C_RED"
export GREEN="$C_GREEN"
export CYAN="$C_CYAN"
export LAVA='\033[38;2;255;77;0m'
export FORREST='\033[38;2;9;28;16m'

ts()       { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
ts_file()  { date -u +"%Y%m%dT%H%M%SZ"; }
ts_local() { date +"%Y-%m-%d %H:%M:%S %Z"; }

log_info()  { printf "%b\n" "${C_CYAN}[INFO]${C_NC} $*"; }
log_ok()    { printf "%b\n" "${C_GREEN}[OK]${C_NC}   $*"; }
log_succ()  { log_ok "$@"; }
log_warn()  { printf "%b\n" "${C_YELLOW}[WARN]${C_NC} $*"; }
log_error() { printf "%b\n" "${C_RED}[ERR]${C_NC}  $*" >&2; return 1; }
log_err()   { log_error "$@"; }
die()       { log_error "$*"; exit 1; }
log_debug() {
    [[ "${TOOL_DEBUG:-0}" = "1" ]] || return 0
    printf "%b\n" "${C_GRAY}[DBG]${C_NC} $*" >&2
}

json_escape() {
    if command -v python3 >/dev/null 2>&1; then
        python3 - <<'PY' "$1"
import json, sys
print(json.dumps(sys.argv[1])[1:-1])
PY
    else
        printf '%s' "$1" | sed 's/\\/\\\\/g;s/"/\\"/g;s/\t/\\t/g;s/\r/\\r/g;s/\n/\\n/g'
    fi
}

# Standard JSON output helpers — use these for --status and machine-readable output
json_ok() {
    printf '{"status":"ok","tool":"%s","node":"%s","exec_id":"%s","data":%s}\n' \
        "${TOOL_NAME:-unknown}" "${NODE_ID:-unknown}" "${EXEC_ID:-}" "${1:-{}}"
}
json_err() {
    printf '{"status":"error","tool":"%s","node":"%s","exec_id":"%s","error":"%s"}\n' \
        "${TOOL_NAME:-unknown}" "${NODE_ID:-unknown}" "${EXEC_ID:-}" "${1:-unknown}" >&2
}

sql_escape() {
    printf '%s' "$1" | tr -d '\0' | sed "s/\\\\/\\\\\\\\/g;s/'/''/g"
}

sha256_text() {
    printf '%s' "$1" | sha256sum | awk '{print $1}'
}

sha256_file() {
    local p="$1"
    [ -f "$p" ] || return 1
    sha256sum "$p" | awk '{print $1}'
}

validate_name() {
    local v="$1" label="${2:-value}"
    [[ -n "$v" && "$v" =~ ^[A-Za-z0-9._-]+$ ]] || { log_error "Invalid ${label}: ${v}"; return 1; }
}

validate_domain() {
    local d="$1"
    [[ -n "$d" && "$d" =~ ^([A-Za-z0-9-]+\.)+[A-Za-z]{2,}$ ]] || { log_error "Invalid domain: ${d}"; return 1; }
}

validate_ip() {
    local ip="$1"
    [[ -n "$ip" ]] || { log_error "Invalid IP address: empty"; return 1; }
    if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
        local IFS=.
        read -r a b c d <<<"$ip"
        for oct in "$a" "$b" "$c" "$d"; do
            [[ "$oct" -ge 0 && "$oct" -le 255 ]] || { log_error "Invalid IP address: ${ip}"; return 1; }
        done
        return 0
    fi
    if [[ "$ip" =~ ^[0-9A-Fa-f:]+$ ]]; then
        return 0
    fi
    log_error "Invalid IP address: ${ip}"
}

validate_port() {
    local p="$1"
    [[ "$p" =~ ^[0-9]+$ && "$p" -ge 1 && "$p" -le 65535 ]] || { log_error "Invalid port: ${p}"; return 1; }
}

safe_path() {
    local base="$1" rel="$2"
    local base_real out
    base_real="$(readlink -f "$base" 2>/dev/null)" || return 1
    out="$(readlink -f "${base_real}/${rel}" 2>/dev/null)" || return 1
    [[ "$out" == "${base_real}/"* || "$out" == "$base_real" ]] || { log_error "Path escape detected: ${rel}"; return 1; }
    printf '%s\n' "$out"
}

reject_webroot_dest() {
    local p="$1"
    case "$p" in
        /var/www/*) log_error "Refusing to write secret material into webroot: ${p}"; return 1 ;;
    esac
}

require_root() {
    [[ "${EUID:-$(id -u)}" -eq 0 ]] || { log_error "Tier 1 Root Access Required"; return 1; }
}

require_user() {
    local expected="$1"
    [[ "$(whoami)" = "$expected" ]] || { log_error "Tier 2 Operator Access Required ($expected)"; return 1; }
}

require_tier() {
    local tier="$1"
    local current_user
    current_user=$(whoami)

    case "$tier" in
        1) # Root Only
            require_root || die "CRITICAL: Tier 1 tools require root access."
            ;;
        2) # Operator (snowdaddy or root)
            if [[ "$current_user" != "snowdaddy" && "$current_user" != "root" ]]; then
                die "CRITICAL: Tier 2 tools require 'snowdaddy' or 'root' access."
            fi
            ;;
        3) # Automation / Auditor
            if [[ "$current_user" = "automation" ]]; then
                require_active_session || die "CRITICAL: Tier 3 automation requires an active hello.sh session."
            fi
            ;;
        *)
            die "INTERNAL ERROR: Unknown security tier '$tier' requested."
            ;;
    esac
}


_get_hello_name() {
    local ej="${TOOLS_ROOT}/../engine.json"
    [[ -f "$ej" ]] || ej="${TOOLS_DIR}/engine.json"
    [[ -f "$ej" ]] || ej="/opt/binary/tools/engine.json"
    [[ -f "$ej" ]] || ej="/opt/unbound/tools/engine.json"
    [[ -f "$ej" ]] || ej="/opt/cache-daemon/tools/engine.json"
    [[ -f "$ej" ]] || ej="/opt/netfilter/tools/engine.json"
    [[ -f "$ej" ]] || ej="/opt/php-worker/tools/engine.json"
    [[ -f "$ej" ]] || ej="/opt/systemd-journal/tools/engine.json"
    [[ -f "$ej" ]] || ej="/opt/stunnel/tools/engine.json"
    if [[ -f "$ej" ]] && command -v python3 &>/dev/null; then
        python3 -c "import json; print(json.load(open('$ej'))['tool_map'].get('b2e0','hello.sh'))" 2>/dev/null
    else
        echo "hello.sh"
    fi
}

require_active_session() {
    local sid="${HELLO_SESSION_ID:-}"
    local hello_bin="${TOOLS_ROOT}/$(_get_hello_name)"
    # Fallback: resolve current session from hello.sh status if env not set
    if [[ -z "$sid" && -x "$hello_bin" ]]; then
        sid="$("$hello_bin" status 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("session_id",""))' 2>/dev/null || true)"
    fi
    [[ -n "$sid" ]] || { log_error "No active maintenance session found. Run hello.sh check-in first."; return 1; }
    "$hello_bin" validate "$sid" >/dev/null 2>&1 || {
        log_error "Active hello session is missing or expired (session_id=${sid})"
        return 1
    }
    export HELLO_SESSION_ID="$sid"
    return 0
}

is_jailed() {
    local tool="${1:-${TOOL_NAME:-UNKNOWN}}"
    [[ -f "${OPT_BASE}/jail/${tool}.jailed" ]]
}

jail_tool() {
    local tool="${1:-${TOOL_NAME:-UNKNOWN}}"
    mkdir -p "${OPT_BASE}/jail"
    : > "${OPT_BASE}/jail/${tool}.jailed"
}

unjail_tool() {
    local tool="${1:-${TOOL_NAME:-UNKNOWN}}"
    rm -f "${OPT_BASE}/jail/${tool}.jailed"
}

require_not_jailed() {
    is_jailed "${1:-${TOOL_NAME:-UNKNOWN}}" && { log_error "${1:-${TOOL_NAME:-UNKNOWN}} is jailed"; return 1; }
    return 0
}

_redacted_detail() {
    local policy="${1:-write}"
    shift || true
    if [[ "$policy" = "sensitive" || "$policy" = "break-glass" ]]; then
        printf '[REDACTED_SENSITIVE_ARGS]'
    else
        printf '%s' "$*"
    fi
}

tool_audit() {
    local action="$1"
    local detail="${2:-}"
    mkdir -p "$AUDIT_DIR"
    local lock="${AUDIT_DIR}/.audit.lock"
    (
        flock -x 200 || exit 1
        local line prev hash
        line="[$(ts)] [${RUN_ID:-NO_ID}] tool=${TOOL_NAME:-unknown} node=${NODE_ID:-unknown} action=${action} detail=$(json_escape "$detail")"
        printf '%s\n' "$line" >> "$AUDIT_LOG"
        prev="GENESIS"
        [[ -s "$AUDIT_HASH_CHAIN" ]] && prev="$(tail -1 "$AUDIT_HASH_CHAIN" | awk '{print $1}')"
        hash="$(printf '%s%s' "$prev" "$line" | sha256sum | awk '{print $1}')"
        printf '%s\n' "$hash" >> "$AUDIT_HASH_CHAIN"
    ) 200>"$lock"
}

emit_finding() {
    local scope="$1" target="$2" expected="$3" observed="$4" severity="${5:-medium}" repairable="${6:-0}"
    command -v sqlite3 >/dev/null 2>&1 || return 0
    [[ -f "$BASELINE_DB" ]] || return 0

    sqlite3 "$BASELINE_DB" <<SQL
INSERT INTO findings(run_id, tool, scope, target, expected, observed, severity, repairable, repaired, status, node, created_at)
VALUES(
  '$(sql_escape "${RUN_ID:-NO_ID}")',
  '$(sql_escape "${TOOL_NAME:-unknown}")',
  '$(sql_escape "$scope")',
  '$(sql_escape "$target")',
  '$(sql_escape "$expected")',
  '$(sql_escape "$observed")',
  '$(sql_escape "$severity")',
  ${repairable},
  0,
  'open',
  '$(sql_escape "${NODE_ID:-unknown}")',
  datetime('now')
);
SQL
}

tool_backup_config() {
    [[ -x "${TOOLS_DIR}/backup.sh" ]] || return 0
    bash "${TOOLS_DIR}/backup.sh" run-configs >/dev/null 2>&1 || true
}

tool_register_file() {
    local path="$1"
    [[ -n "$path" && -e "$path" && -x "${TOOLS_DIR}/files.sh" ]] || return 0
    bash "${TOOLS_DIR}/files.sh" register "$path" >/dev/null 2>&1 || true
}

tool_verify_files() {
    local registry="${OPT_BASE}/data/file-registry.db"
    if ! command -v sqlite3 >/dev/null 2>&1 || [[ ! -f "$registry" ]]; then
        log_warn "File registry unavailable"
        return 1
    fi

    sqlite3 "$registry" "SELECT path, sha256 FROM file_registry;" 2>/dev/null | \
    while IFS='|' read -r path expected; do
        [[ -f "$path" ]] || { echo "MISSING $path"; continue; }
        local actual
        actual="$(sha256_file "$path")"
        [[ "$actual" == "$expected" ]] || echo "CHANGED $path"
    done
}

tool_enable_cli_log() {
    [[ "${TOOL_JSON:-0}" = "1" ]] && return 0
    [[ "${_CLI_LOGGED:-0}" = "1" ]] && return 0
    [[ -n "${TOOL_LOG:-}" ]] || return 0
    mkdir -p "$(dirname "$TOOL_LOG")" 2>/dev/null
    export _CLI_LOGGED=1
    exec > >(tee -a "$TOOL_LOG") 2>&1
}


honeypot_log() {
    local entry="$1"
    mkdir -p "$(dirname "$HONEYPOT_LOG")" 2>/dev/null
    printf '%s\n' "$entry" >> "$HONEYPOT_LOG"
}

tool_notify() {
    local title="$1"
    local message="$2"
    local level="${3:-info}"
    local ts_now
    ts_now="$(ts_file)"
    local notify_file="${NOTIFY_DIR}/${level}_$(echo "${title}" | tr -dc 'a-zA-Z0-9._-')_${ts_now}.notif"
    mkdir -p "$NOTIFY_DIR"
    {
        echo "TITLE=${title}"
        echo "LEVEL=${level}"
        echo "MESSAGE=${message}"
    } > "$notify_file"
    chmod 600 "$notify_file" 2>/dev/null || true
    if [[ "$level" = "critical" || "$level" = "high" ]]; then
        tool_audit "notify_${level}" "${title}"
    fi
}

tool_init() {
    mkdir -p "${LOG_DIR}/tools" "${DATA_DIR}" "${NOTIFY_DIR}" "${RUN_DIR}" "/var/log/365" "$(dirname "$HONEYPOT_LOG")" 2>/dev/null
    export NODE_ID="${NODE_ID:-$(hostname -s 2>/dev/null || echo unknown)}"
    export RUN_ID="${RUN_ID:-${TOOL_NAME:-tool}-$(ts_file)-$$}"
    # Per-execution UUID for traceability across Phoenix → agent → tool → DB → dashboard
    if command -v uuidgen &>/dev/null; then
        export EXEC_ID="${EXEC_ID:-$(uuidgen)}"
    else
        export EXEC_ID="${EXEC_ID:-$(cat /proc/sys/kernel/random/uuid 2>/dev/null || echo ${RUN_ID})}"
    fi
    tool_enable_cli_log
    # Auto-start timing for tool_complete() trap
    if declare -F tool_start >/dev/null 2>&1; then
        tool_start
    fi
}

# -------------------------------------------------------------------------
# Database Logging Integration
# -------------------------------------------------------------------------
# Source log_to_db.sh if present. This provides:
#   log_to_db()     -- Non-blocking insert to Alpha's tool_executions table
#   tool_wrap()     -- Execute + capture timing + auto-log to DB
#   tool_complete() -- Log final exit code (use in EXIT trap)
#   tool_start()    -- Start timing (called automatically by tool_init)
#
# If log_to_db.sh is not deployed to this node yet, these functions
# are defined as no-ops so existing tools continue working.
# -------------------------------------------------------------------------
_log_to_db_path="${TOOLS_ROOT}/log_to_db.sh"
if [[ -f "$_log_to_db_path" ]]; then
    # shellcheck source=log_to_db.sh
    source "$_log_to_db_path"
else
    # No-op stubs until log_to_db.sh is deployed
    log_to_db()      { :; }
    tool_wrap()       { shift; "$@"; }
    tool_complete()   { return $?; }
    tool_start()      { :; }
fi

handle_standard_command() {
    local cmd="${1:-help}"
    shift || true

    case "$cmd" in
        help)
            declare -F tool_help >/dev/null && tool_help
            ;;
        start|stop|status|restart|verify|watch|backup|restore)
            local fn="tool_${cmd//-/_}"
            if declare -F "$fn" >/dev/null; then
                "$fn" "$@"
            else
                log_error "Standard command '${cmd}' not implemented for ${TOOL_NAME:-tool}"
                return 1
            fi
            ;;
        jail)
            jail_tool
            log_ok "Jailed ${TOOL_NAME:-tool}"
            ;;
        unjail)
            unjail_tool
            log_ok "Unjailed ${TOOL_NAME:-tool}"
            ;;
        log|logs)
            if [[ -n "${TOOL_LOG:-}" && -f "${TOOL_LOG:-}" ]]; then
                tail -f "$TOOL_LOG"
            else
                log_error "No TOOL_LOG defined"
                return 1
            fi
            ;;
        *)
            log_error "Unknown command: $cmd"
            return 1
            ;;
    esac
}

# =============================================================================
# Source supplemental libraries (non-blocking, fail-safe)
# =============================================================================

# Plaintext tool execution logging to Engine DB (sec_tool_runs)
[[ -f "${TOOLS_DIR}/log_to_db.sh" ]] && source "${TOOLS_DIR}/log_to_db.sh" 2>/dev/null || true

# SCLS encrypted audit logging to Engine DB (sec_audit_log)
[[ -f "${TOOLS_DIR}/audit_logger.sh" ]] && source "${TOOLS_DIR}/audit_logger.sh" 2>/dev/null || true
