# Copyright (c) 2026 Progralink. Licensed under the DevCage License — see LICENSE.
# shellcheck shell=bash
#
# logging.sh — Session log management for the DevCage bash launcher.
# Sourced by devcage.sh. Depends on: SESSION_LOG, SESSION_TS, LOG_DIR, LOG_KEEP,
# WORKSPACE, DEVCAGE_VERSION, CONTAINER_ENGINE, RESOLVED_MEMORY, RESOLVED_CPUS,
# EMBED_DOCKER, DEVCAGE_IMAGE, COMPOSE_FILES, SANDBOX_TMPDIR, SANDBOX_MEMORY_SWAP.

# ── Core log primitives ───────────────────────────────────────────

_log() {
    if [ "${SESSION_LOG:-}" != "" ]; then printf '%s\n' "$*" >> "$SESSION_LOG" 2>/dev/null || true; fi
}
_log_section() {
    _log ""
    _log "=== $* ==="
}

# ── Session log initialization ────────────────────────────────────
# Sets SESSION_LOG, creates the log file, writes the session header.
# Also adds DEVCAGE_SESSION_LOG to RUN_ARGS for in-container logging.
# Callers must have SESSION_TS, LOG_KEEP, WORKSPACE set before calling.
_init_session_log() {
    SESSION_TS="$(date '+%Y%m%d-%H%M%S')-$$"
    LOG_KEEP="${LOG_KEEP:-10}"
    LOG_DIR="${WORKSPACE}/.devcage/logs"
    mkdir -p "$LOG_DIR" 2>/dev/null || true
    if [ ! -f "${LOG_DIR}/.gitignore" ]; then
        printf '*\n' > "${LOG_DIR}/.gitignore" 2>/dev/null || true
    fi
    SESSION_LOG="${LOG_DIR}/session-${SESSION_TS}.log"
    # Create log with restricted permissions (owner-only read/write)
    ( umask 077; set -C; : > "$SESSION_LOG" ) 2>/dev/null || {
        SESSION_LOG="$(mktemp "${LOG_DIR}/session-${SESSION_TS}-XXXXXXXX.log")"
        chmod 600 "$SESSION_LOG"
    }
    ln -sf "$(basename "$SESSION_LOG")" "${LOG_DIR}/latest" 2>/dev/null || true

    # Session log path (passed to entrypoint for in-container logging)
    if [ "${SESSION_LOG:-}" != "" ]; then
        RUN_ARGS+=(-e "DEVCAGE_SESSION_LOG=/workspace/.devcage/logs/session-${SESSION_TS}.log")
    fi

    # Write session header
    _log_section "DevCage Session"
    _log "Timestamp : $(date '+%Y-%m-%d %H:%M:%S %Z')"
    _log "Version   : ${DEVCAGE_VERSION}"
    _log "Engine    : ${CONTAINER_ENGINE}"
    _log "Workspace : ${WORKSPACE}"
    _log "Command   : $*"
    _log "Memory    : ${RESOLVED_MEMORY}"
    _log "CPUs      : ${RESOLVED_CPUS}"
    _dind_hdr="off"
    [ "$EMBED_DOCKER" = "1" ] && _dind_hdr="on"
    _log "DinD      : ${_dind_hdr}"
    _log "Image     : ${DEVCAGE_IMAGE:-devcage-workspace}"
    _log "Host PID  : $$"
    _log "==========================="
}

# ── Session footer ────────────────────────────────────────────────
# Writes exit code, docker events, OOM detection, and timing to session log.
# Callers must pass the exit code as $1.
_write_session_footer() {
    local _exit_code="${1:-0}"
    [ "${SESSION_LOG:-}" = "" ] && return 0

    _log_section "Session End"
    _log "Exit code : ${_exit_code}"
    _log "Ended at  : $(date '+%Y-%m-%d %H:%M:%S %Z')"
    case "$_exit_code" in
        0)   _log "Status    : clean exit" ;;
        130) _log "Status    : interrupted (SIGINT)" ;;
        137)
            # Check actual OOM status — exit 137 from docker exec in reuse mode
            # can mean idle timeout or docker stop, not necessarily OOM.
            local _oom_status="unknown"
            if [ "${_REUSE_CONTAINER_ID:-}" != "" ]; then
                _oom_status=$("$CONTAINER_ENGINE" inspect --format '{{.State.OOMKilled}}' "$_REUSE_CONTAINER_ID" 2>/dev/null || echo "unknown")
            fi
            if [ "$_oom_status" = "true" ]; then
                _log "Status    : killed (OOM — out of memory)"
            elif [ "$_oom_status" = "false" ]; then
                _log "Status    : killed (container stopped — idle timeout or manual stop)"
            else
                _log "Status    : killed (SIGKILL — likely OOM or forced termination)"
            fi
            ;;
        143) _log "Status    : terminated (SIGTERM)" ;;
        *)   _log "Status    : exited with code ${_exit_code}" ;;
    esac

    # Capture recent docker events for this workspace (OOM, die, kill)
    if [ "$CONTAINER_ENGINE" = "docker" ]; then
        _events="$("$CONTAINER_ENGINE" events --since 5m --until 0s \
            --filter "label=dev-sandbox.workspace=${WORKSPACE}" \
            --format '{{.Time}} {{.Type}} {{.Action}} {{.Actor.Attributes}}' 2>/dev/null || true)"
        if [ "$_events" != "" ]; then
            _log ""
            _log "--- Docker Events (last 5 min) ---"
            _log "$_events"
        fi
    fi

    # Check if entrypoint init completed
    if ! grep -q "Initialization complete" "$SESSION_LOG" 2>/dev/null; then
        _log ""
        _log "WARNING: Entrypoint initialization did not complete. Check entrypoint errors above."
    fi

    # OOM hint — only when actually OOM-killed or when OOM status is unknown
    if [ "$_exit_code" = "137" ]; then
        local _show_oom_hint=1
        if [ "${_REUSE_CONTAINER_ID:-}" != "" ]; then
            local _oom_check
            _oom_check=$("$CONTAINER_ENGINE" inspect --format '{{.State.OOMKilled}}' "$_REUSE_CONTAINER_ID" 2>/dev/null || echo "unknown")
            [ "$_oom_check" = "false" ] && _show_oom_hint=0
        fi
        if [ "$_show_oom_hint" = "1" ]; then
            _log ""
            _log "NOTE: Container memory limit was ${RESOLVED_MEMORY} (+swap to ${SANDBOX_MEMORY_SWAP}). Try --memory with a higher value."
        fi
    fi

    _rotate_session_logs
}

# ── Log rotation ──────────────────────────────────────────────────
# Keeps only LOG_KEEP most recent session-*.log files, removes older ones.
_rotate_session_logs() {
    [ "${LOG_DIR:-}" = "" ] && return 0
    _log_count="$(find "$LOG_DIR" -maxdepth 1 -name 'session-*.log' 2>/dev/null | wc -l)"
    if [ "$_log_count" -gt "$LOG_KEEP" ]; then
        _to_remove=$(( _log_count - LOG_KEEP ))
        find "$LOG_DIR" -maxdepth 1 -name 'session-*.log' 2>/dev/null | sort | head -n "$_to_remove" | while IFS= read -r _old; do
            rm -f "$_old" 2>/dev/null || true
        done
    fi
}

# ── Exit cleanup ──────────────────────────────────────────────────
# Release build lock, stop DinD sidecar, remove SANDBOX_TMPDIR (shredding sensitive files).
cleanup_on_exit() {
    # Release build lock if held (safety net for abnormal exit)
    _release_build_lock 2>/dev/null || true
    # Stop the dind sidecar if it was started
    if [ "$EMBED_DOCKER" = "1" ]; then
        _compose_cleanup "${COMPOSE_FILES[@]}"
    fi
    # Remove per-sandbox temp directory (shred sensitive files first)
    if [ "${SANDBOX_TMPDIR:-}" != "" ] && [ -d "$SANDBOX_TMPDIR" ]; then
        for _sensitive_file in "${SANDBOX_TMPDIR}/.credentials.json" "${SANDBOX_TMPDIR}/.git-env"; do
            if [ -f "$_sensitive_file" ]; then
                if command -v shred >/dev/null 2>&1; then
                    shred -fuz "$_sensitive_file" 2>/dev/null || rm -f "$_sensitive_file" 2>/dev/null || true
                else
                    dd if=/dev/zero of="$_sensitive_file" bs=1 count="$(wc -c < "$_sensitive_file")" conv=notrunc 2>/dev/null || true
                    rm -f "$_sensitive_file" 2>/dev/null || true
                fi
            fi
        done
        rm -rf "$SANDBOX_TMPDIR" 2>/dev/null || true
    fi
}
