#!/usr/bin/env bash
# =============================================================================
# /opt/binary/tools/log_to_db.sh -- Tool Execution Database Logger
# =============================================================================
# Sourced by common.sh to provide log_to_db() and tool_wrap() functions.
# Every tool invocation that uses tool_init → tool_wrap automatically logs
# to 558201's sec_tool_runs table via WireGuard (wg7 mesh).
#
# Design decisions:
#   - Non-blocking: DB inserts run in background (&) so tools never stall
#   - Fail-safe: if DB is unreachable, tool execution continues unaffected
#   - Credentials: loaded from /etc/binary/tooling.env (created by password.sh)
#   - Identity: NODE_ID, SUBMITTED_BY, BIRD_ID set by caller or auto-detected
# =============================================================================

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

# Database target (558201 MariaDB via WireGuard wg7 mesh)
readonly TOOLING_DB_HOST="${TOOLING_DB_HOST:-172.28.50.1}"
readonly TOOLING_DB_PORT="${TOOLING_DB_PORT:-3306}"
readonly TOOLING_DB_NAME="${TOOLING_DB_NAME:-shared_engine}"
readonly TOOLING_DB_USER="${TOOLING_DB_USER:-tooling}"

# Load credentials from env file (created by password.sh)
_load_tooling_creds() {
    if [[ -z "${TOOLING_DB_PASS:-}" && -f /etc/binary/tooling.env ]]; then
        # shellcheck source=/dev/null
        source /etc/binary/tooling.env 2>/dev/null || true
    fi
}

# -------------------------------------------------------------------------
# log_to_db -- Insert a tool execution record into Alpha's tool_executions
# -------------------------------------------------------------------------
# Usage: log_to_db "function_name" "ok|error|timeout|skipped" exit_code duration_ms ["stdout_summary"] ["stderr_summary"]
#
# This function is NON-BLOCKING. It fires the insert in the background
# and returns immediately. If the DB is unreachable, it logs to stderr
# only when TOOL_DEBUG=1.
# -------------------------------------------------------------------------
log_to_db() {
    local tool_function="${1:-unknown}"
    local status="${2:-ok}"
    local exit_code="${3:-0}"
    local duration_ms="${4:-0}"
    local stdout_summary="${5:-}"
    local stderr_summary="${6:-}"

    # Resolve identity
    local tool_name="${TOOL_NAME:-unknown}"
    local node="${NODE_ID:-$(hostname -s 2>/dev/null || echo unknown)}"
    local submitted_by="${SUBMITTED_BY:-cron:${node}}"
    local bird_id="${BIRD_ID:-}"

    # Event contract fields (set via env or defaults)
    local execution_id="${EXECUTION_ID:-$(cat /proc/sys/kernel/random/uuid 2>/dev/null || echo '')}"
    local action="${TOOL_ACTION:-}"
    local policy="${TOOL_POLICY:-}"
    local used_by="${TOOL_USED_BY:-${submitted_by}}"
    local source_engine="${TOOL_SOURCE_ENGINE:-}"
    local args_hash="${TOOL_ARGS_HASH:-}"
    local signed_by="${TOOL_SIGNED_BY:-}"
    local signature="${TOOL_SIGNATURE:-}"

    # Truncate summaries to 500 chars (matches DB column constraint)
    stdout_summary="${stdout_summary:0:500}"
    stderr_summary="${stderr_summary:0:500}"

    _load_tooling_creds

    # If no password available, silently skip
    if [[ -z "${TOOLING_DB_PASS:-}" ]]; then
        [[ "${TOOL_DEBUG:-0}" = "1" ]] && log_debug "log_to_db: TOOLING_DB_PASS not set, skipping DB insert"
        return 0
    fi

    # Build optional column/value fragments for event contract
    local extra_cols="" extra_vals=""
    if [[ -n "$execution_id" ]]; then
        extra_cols="${extra_cols}, execution_id"
        extra_vals="${extra_vals}, '$(sql_escape "$execution_id")'"
    fi
    if [[ -n "$action" ]]; then
        extra_cols="${extra_cols}, action"
        extra_vals="${extra_vals}, '$(sql_escape "$action")'"
    fi
    if [[ -n "$policy" ]]; then
        extra_cols="${extra_cols}, policy"
        extra_vals="${extra_vals}, '$(sql_escape "$policy")'"
    fi
    if [[ -n "$used_by" ]]; then
        extra_cols="${extra_cols}, used_by"
        extra_vals="${extra_vals}, '$(sql_escape "$used_by")'"
    fi
    if [[ -n "$source_engine" ]]; then
        extra_cols="${extra_cols}, source_engine"
        extra_vals="${extra_vals}, '$(sql_escape "$source_engine")'"
    fi
    if [[ -n "$args_hash" ]]; then
        extra_cols="${extra_cols}, args_hash"
        extra_vals="${extra_vals}, '$(sql_escape "$args_hash")'"
    fi
    if [[ -n "$signed_by" ]]; then
        extra_cols="${extra_cols}, signed_by"
        extra_vals="${extra_vals}, '$(sql_escape "$signed_by")'"
    fi
    if [[ -n "$signature" ]]; then
        extra_cols="${extra_cols}, signature"
        extra_vals="${extra_vals}, '$(sql_escape "$signature")'"
    fi

    # Non-blocking insert via mysql client, with local spool fallback
    local SPOOL_DIR="/opt/binary/data/tool-execution-spool"
    {
        if MYSQL_PWD="${TOOLING_DB_PASS}" mysql -h "$TOOLING_DB_HOST" -P "$TOOLING_DB_PORT" \
              -u "$TOOLING_DB_USER" "$TOOLING_DB_NAME" \
              --connect-timeout=3 -e "
            INSERT INTO sec_tool_runs
                (node_id, tool_name, tool_function, submitted_by, bird_id,
                 status, exit_code, duration_ms, stdout_summary, stderr_summary, status_code,
                 started_at, finished_at${extra_cols})
            VALUES
                ('$(sql_escape "$node")', '$(sql_escape "$tool_name")', '$(sql_escape "$tool_function")',
                 '$(sql_escape "$submitted_by")',
                 $(if [[ -n "$bird_id" ]]; then echo "'$(sql_escape "$bird_id")'"; else echo "NULL"; fi),
                 '$(sql_escape "$status")', $exit_code, $duration_ms,
                 '$(sql_escape "$stdout_summary")',
                 '$(sql_escape "$stderr_summary")', 1,
                 DATE_SUB(NOW(), INTERVAL $duration_ms/1000 SECOND), NOW()${extra_vals})
        " 2>/dev/null; then
            : # success
        else
            # DB unreachable — spool locally for later flush
            mkdir -p "$SPOOL_DIR" 2>/dev/null
            local spool_file="${SPOOL_DIR}/$(date -u +%Y%m%d%H%M%S)-${tool_name}-${tool_function}-$$.json"
            printf '{"tool_name":"%s","tool_function":"%s","server_node":"%s","submitted_by":"%s","bird_id":"%s","status":"%s","exit_code":%d,"duration_ms":%d,"stdout_summary":"%s","stderr_summary":"%s","execution_id":"%s","action":"%s","policy":"%s","used_by":"%s","source_engine":"%s","spooled_at":"%s"}\n' \
                "$tool_name" "$tool_function" "$node" "$submitted_by" "$bird_id" \
                "$status" "$exit_code" "$duration_ms" \
                "$(echo "$stdout_summary" | head -c 200)" \
                "$(echo "$stderr_summary" | head -c 200)" \
                "$execution_id" "$action" "$policy" "$used_by" "$source_engine" \
                "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
                > "$spool_file" 2>/dev/null
        fi
    } &
    disown 2>/dev/null

    return 0
}

# -------------------------------------------------------------------------
# tool_wrap -- Execute a function and automatically log the result
# -------------------------------------------------------------------------
# Usage: tool_wrap "function_name" function_to_call [args...]
#
# Captures timing, exit code, stdout/stderr summaries, and logs to DB.
# This is the preferred way to wrap any tool function for full observability.
# -------------------------------------------------------------------------
tool_wrap() {
    local func_label="${1:?tool_wrap requires a function label}"
    shift

    local start_ns
    start_ns=$(date +%s%N 2>/dev/null || echo 0)

    local _tw_stdout _tw_stderr _tw_exit
    _tw_stdout=$(mktemp 2>/dev/null || echo "/tmp/.tw_out_$$")
    _tw_stderr=$(mktemp 2>/dev/null || echo "/tmp/.tw_err_$$")

    # Execute the function/command, capturing output
    "$@" > "$_tw_stdout" 2> "$_tw_stderr"
    _tw_exit=$?

    local end_ns
    end_ns=$(date +%s%N 2>/dev/null || echo 0)

    # Calculate duration in milliseconds
    local duration_ms=0
    if [[ "$start_ns" != "0" && "$end_ns" != "0" ]]; then
        duration_ms=$(( (end_ns - start_ns) / 1000000 ))
    fi

    # Determine status from exit code
    local status="ok"
    if [[ $_tw_exit -ne 0 ]]; then
        status="error"
    fi

    # Capture summaries (first 500 chars)
    local out_summary="" err_summary=""
    if [[ -f "$_tw_stdout" ]]; then
        out_summary=$(head -c 500 "$_tw_stdout" 2>/dev/null || true)
        cat "$_tw_stdout"  # Still print to actual stdout
    fi
    if [[ -f "$_tw_stderr" ]]; then
        err_summary=$(head -c 500 "$_tw_stderr" 2>/dev/null || true)
        cat "$_tw_stderr" >&2  # Still print to actual stderr
    fi

    # Cleanup temp files
    rm -f "$_tw_stdout" "$_tw_stderr" 2>/dev/null

    # Log to database
    log_to_db "$func_label" "$status" "$_tw_exit" "$duration_ms" "$out_summary" "$err_summary"

    return $_tw_exit
}

# -------------------------------------------------------------------------
# tool_complete -- Log completion at end of a tool run (call in EXIT trap)
# -------------------------------------------------------------------------
# Usage: trap 'tool_complete' EXIT
#
# Automatically logs the final exit code of the tool.
# -------------------------------------------------------------------------
_TOOL_START_NS=""

tool_start() {
    _TOOL_START_NS=$(date +%s%N 2>/dev/null || echo 0)
}

tool_complete() {
    local final_exit=$?
    local end_ns
    end_ns=$(date +%s%N 2>/dev/null || echo 0)

    local duration_ms=0
    if [[ -n "$_TOOL_START_NS" && "$_TOOL_START_NS" != "0" && "$end_ns" != "0" ]]; then
        duration_ms=$(( (end_ns - _TOOL_START_NS) / 1000000 ))
    fi

    local status="ok"
    [[ $final_exit -ne 0 ]] && status="error"

    log_to_db "complete" "$status" "$final_exit" "$duration_ms"
    return $final_exit
}
