#!/usr/bin/env bash
# Collect a privacy-conscious, read-only RK1 media diagnostic bundle.

set -Eeuo pipefail
export LC_ALL=C
umask 077

OUTPUT=""
DIRECTORY_OUTPUT=0
ACTIVE_TESTS=0
MEDIA_DIR=""
TEMP_DIR=""

SYS_ROOT=${RK1_SYSFS_ROOT:-/sys}
PROC_ROOT=${RK1_PROCFS_ROOT:-/proc}
DEV_ROOT=${RK1_DEV_ROOT:-/dev}
RKNN_HOME=${RKNN_HOME:-/opt/rknn/current}

usage() {
    cat <<'EOF'
Usage: rk1-media-diagnostics [OPTIONS]

Options:
  --output PATH          Output archive or directory path.
  --directory            Write an unpacked directory instead of a .tar.gz.
  --include-active-tests Include short Vulkan, encode, and NPU workloads.
  --media-dir PATH       Pass media samples to active decode/RGA tests.
  -h, --help             Show this help.

The bundle is local only. It deliberately excludes environment variables,
home-directory contents, SSH material, disk serials/UUIDs, MAC addresses,
IP addresses, raw EDID, and unfiltered system journals. Review it before
sharing it with anyone.
EOF
}

die() {
    printf 'rk1-media-diagnostics: %s\n' "$*" >&2
    exit 2
}

while (($#)); do
    case "$1" in
        --output)
            (($# >= 2)) || die "--output requires a path"
            OUTPUT=$2
            shift 2
            ;;
        --directory)
            DIRECTORY_OUTPUT=1
            shift
            ;;
        --include-active-tests)
            ACTIVE_TESTS=1
            shift
            ;;
        --media-dir)
            (($# >= 2)) || die "--media-dir requires a path"
            MEDIA_DIR=$2
            shift 2
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            die "unknown option: $1"
            ;;
    esac
done

if [[ -n "$MEDIA_DIR" && ! -d "$MEDIA_DIR" ]]; then
    die "media directory is not a directory: $MEDIA_DIR"
fi

safe_host=$(hostname 2>/dev/null || printf rk1)
safe_host=${safe_host//[^A-Za-z0-9._-]/_}
timestamp=$(date -u +%Y%m%dT%H%M%SZ)
if [[ -z "$OUTPUT" ]]; then
    if ((DIRECTORY_OUTPUT)); then
        OUTPUT="$PWD/rk1-media-diagnostics-${safe_host}-${timestamp}"
    else
        OUTPUT="$PWD/rk1-media-diagnostics-${safe_host}-${timestamp}.tar.gz"
    fi
fi

output_parent=$(dirname -- "$OUTPUT")
[[ -d "$output_parent" ]] || die "output parent does not exist: $output_parent"
output_parent=$(cd -- "$output_parent" && pwd -P)
OUTPUT="$output_parent/$(basename -- "$OUTPUT")"
[[ ! -e "$OUTPUT" && ! -L "$OUTPUT" ]] || die "refusing to overwrite $OUTPUT"

TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rk1-media-diagnostics.XXXXXXXX")
REPORT="$TEMP_DIR/report"
mkdir -p "$REPORT"

cleanup() {
    if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
        rm -rf -- "$TEMP_DIR"
    fi
}
trap cleanup EXIT

sanitize_stream() {
    # Redact common MAC, IPv4, root-device, and static-IP command-line forms.
    sed -E \
        -e 's/([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}/<mac-redacted>/g' \
        -e 's/([[:space:]=]|^)([0-9]{1,3}\.){3}[0-9]{1,3}([[:space:]\/:]|$)/\1<ip-redacted>\3/g' \
        -e 's/(root=)[^[:space:]]+/\1<root-device-redacted>/g' \
        -e 's/(ip=)[^[:space:]]+/\1<ip-config-redacted>/g' \
        -e 's/(UUID|PARTUUID)=[A-Za-z0-9-]+/\1=<redacted>/g' \
        -e 's/([Ss]erial([ _-]?[Nn]umber)?[=:][[:space:]]*)[^[:space:],;]+/\1<redacted>/g'
}

run_capture() {
    local destination=$1
    shift
    {
        printf '$'
        printf ' %q' "$@"
        printf '\n'
        if command -v "$1" >/dev/null 2>&1; then
            timeout 45s "$@" 2>&1 ||
                printf '[command exited %d]\n' "$?"
        else
            printf '[command unavailable: %s]\n' "$1"
        fi
    } | sanitize_stream >"$REPORT/$destination"
}

append_command() {
    local destination=$1
    shift
    {
        printf '\n$'
        printf ' %q' "$@"
        printf '\n'
        if command -v "$1" >/dev/null 2>&1; then
            timeout 45s "$@" 2>&1 ||
                printf '[command exited %d]\n' "$?"
        else
            printf '[command unavailable: %s]\n' "$1"
        fi
    } | sanitize_stream >>"$REPORT/$destination"
}

cat >"$REPORT/README.txt" <<'EOF'
RK1 media diagnostic bundle

This is a read-only snapshot for diagnosing HDMI, GPU, RKMPP VPU, RGA,
RKNPU, ALSA, Ethernet, and storage enumeration. Commands that were missing or
permission-denied are recorded rather than treated as collector failures.

The collector attempts to redact IP and MAC addresses and omits raw EDID,
disk serials/UUIDs, environment variables, home directories, SSH material,
and unfiltered journals. Review every file before sharing the bundle.
EOF

{
    printf 'collected_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
    printf 'collector_version=1\n'
    printf 'active_tests=%s\n' "$ACTIVE_TESTS"
    printf 'kernel='
    uname -srvm 2>/dev/null || true
    printf 'architecture='
    uname -m 2>/dev/null || true
    if [[ -r /etc/os-release ]]; then
        printf '\n[os-release]\n'
        grep -E '^(NAME|VERSION|VERSION_ID|ID|ID_LIKE)=' /etc/os-release || true
    fi
    if [[ -r "$PROC_ROOT/device-tree/model" ]]; then
        printf '\nboard_model='
        tr -d '\000' <"$PROC_ROOT/device-tree/model" || true
        printf '\n'
    fi
    if [[ -r "$PROC_ROOT/cmdline" ]]; then
        printf '\n[kernel-command-line-redacted]\n'
        sanitize_stream <"$PROC_ROOT/cmdline"
        printf '\n'
    fi
} >"$REPORT/system.txt"

SELFTEST=""
if command -v rk1-media-selftest >/dev/null 2>&1; then
    SELFTEST=$(command -v rk1-media-selftest)
elif [[ -x "$(dirname -- "${BASH_SOURCE[0]}")/rk1-media-selftest" ]]; then
    SELFTEST="$(dirname -- "${BASH_SOURCE[0]}")/rk1-media-selftest"
fi

if [[ -n "$SELFTEST" ]]; then
    selftest_args=(--json)
    if ((ACTIVE_TESTS == 0)); then
        selftest_args+=(--quick)
    fi
    if [[ -n "$MEDIA_DIR" ]]; then
        selftest_args+=(--media-dir "$MEDIA_DIR")
    fi
    if ! timeout 600s "$SELFTEST" "${selftest_args[@]}" \
        >"$REPORT/selftest.json" 2>"$REPORT/selftest.stderr"; then
        printf 'self-test returned a nonzero status; inspect its JSON and stderr\n' \
            >"$REPORT/selftest-status.txt"
    fi
else
    printf '{"error":"rk1-media-selftest is unavailable"}\n' \
        >"$REPORT/selftest.json"
fi

# DRM connector details. EDID is represented by size, hash, and decoded text,
# never by the raw binary blob.
{
    shopt -s nullglob
    connectors=("$SYS_ROOT"/class/drm/card*-*)
    shopt -u nullglob
    if ((${#connectors[@]} == 0)); then
        printf 'No DRM connectors found.\n'
    fi
    for connector in "${connectors[@]}"; do
        [[ -d "$connector" ]] || continue
        printf '\n[%s]\n' "${connector##*/}"
        for attribute in status enabled dpms link_status; do
            if [[ -r "$connector/$attribute" ]]; then
                printf '%s=' "$attribute"
                tr -d '\000' <"$connector/$attribute" 2>/dev/null || true
                printf '\n'
            fi
        done
        if [[ -r "$connector/modes" ]]; then
            printf 'modes:\n'
            sed 's/^/  /' "$connector/modes" 2>/dev/null || true
        fi
        if [[ -s "$connector/edid" ]]; then
            printf 'edid_size=%s\n' "$(wc -c <"$connector/edid")"
            printf 'edid_sha256=%s\n' \
                "$(sha256sum "$connector/edid" | awk '{print $1}')"
            if command -v edid-decode >/dev/null 2>&1; then
                printf 'decoded_edid:\n'
                timeout 15s edid-decode "$connector/edid" 2>&1 |
                    sed 's/^/  /' || true
            fi
        fi
    done
} | sanitize_stream >"$REPORT/drm-connectors.txt"

run_capture drm-modetest.txt modetest -c -p
run_capture kernel-modules.txt lsmod

{
    printf '[platform GPU driver bindings]\n'
    for driver in panthor panfrost mali; do
        directory="$SYS_ROOT/bus/platform/drivers/$driver"
        [[ -d "$directory" ]] || continue
        printf '%s:\n' "$driver"
        find "$directory" -mindepth 1 -maxdepth 1 \( -type l -o -type d \) \
            -printf '  %f\n' 2>/dev/null | sort
    done
    printf '\n[GPU devfreq]\n'
    shopt -s nullglob
    gpu_nodes=("$SYS_ROOT"/class/devfreq/*.gpu)
    shopt -u nullglob
    for node in "${gpu_nodes[@]}"; do
        printf '%s\n' "${node##*/}"
        for attribute in cur_freq min_freq max_freq governor available_frequencies; do
            if [[ -r "$node/$attribute" ]]; then
                printf '  %s=' "$attribute"
                tr -d '\000' <"$node/$attribute" 2>/dev/null || true
                printf '\n'
            fi
        done
    done
} >"$REPORT/gpu-sysfs.txt"
run_capture vulkan.txt vulkaninfo --summary
append_command vulkan.txt eglinfo -B

{
    printf '[media and accelerator platform bindings]\n'
    for pattern in 'mpp*' 'rga*' 'rockchip-rga' 'RKNPU' 'rknpu'; do
        shopt -s nullglob
        directories=("$SYS_ROOT"/bus/platform/drivers/$pattern)
        shopt -u nullglob
        for directory in "${directories[@]}"; do
            [[ -d "$directory" ]] || continue
            printf '\n%s:\n' "${directory##*/}"
            find "$directory" -mindepth 1 -maxdepth 1 \( -type l -o -type d \) \
                -printf '  %f\n' 2>/dev/null | sort
        done
    done
    printf '\n[device nodes]\n'
    shopt -s nullglob
    nodes=("$DEV_ROOT"/dri/* "$DEV_ROOT"/mpp_service \
           "$DEV_ROOT"/rga "$DEV_ROOT"/rknpu* "$DEV_ROOT"/mali*)
    shopt -u nullglob
    for node in "${nodes[@]}"; do
        stat -c '%A %U:%G %t:%T %n' "$node" 2>/dev/null || true
    done
} >"$REPORT/accelerators.txt"

FFMPEG=""
for candidate in /opt/rkmedia/bin/ffmpeg-rk /usr/local/bin/ffmpeg-rk; do
    if [[ -x "$candidate" ]]; then
        FFMPEG=$candidate
        break
    fi
done
if [[ -z "$FFMPEG" ]] && command -v ffmpeg-rk >/dev/null 2>&1; then
    FFMPEG=$(command -v ffmpeg-rk)
fi
if [[ -n "$FFMPEG" ]]; then
    run_capture ffmpeg-rk.txt "$FFMPEG" -hide_banner -version
    append_command ffmpeg-rk.txt "$FFMPEG" -hide_banner -hwaccels
    {
        printf '\n[Rockchip encoders, decoders, and filters]\n'
        "$FFMPEG" -hide_banner -decoders 2>/dev/null |
            grep -Ei 'h264|hevc|vp9|av1|rkmpp' || true
        "$FFMPEG" -hide_banner -encoders 2>/dev/null |
            grep -Ei 'h264|hevc|mjpeg|rkmpp' || true
        "$FFMPEG" -hide_banner -filters 2>/dev/null |
            grep -Ei 'rkrga|drm' || true
    } >>"$REPORT/ffmpeg-rk.txt"
else
    printf 'ffmpeg-rk is unavailable\n' >"$REPORT/ffmpeg-rk.txt"
fi

{
    printf '[RKNN installation]\n'
    if [[ -r "$RKNN_HOME/manifest.env" ]]; then
        sed -E 's#(REPOSITORY=).*#\1<upstream-url>#' "$RKNN_HOME/manifest.env"
    else
        printf 'manifest unavailable at %s\n' "$RKNN_HOME/manifest.env"
    fi
    for asset in \
        "$RKNN_HOME/lib/librknnrt.so" \
        "$RKNN_HOME/share/models/rk3588/mobilenet_v1.rknn" \
        "$RKNN_HOME/bin/rknn-inference-test"; do
        if [[ -r "$asset" ]]; then
            sha256sum "$asset"
        else
            printf 'missing: %s\n' "$asset"
        fi
    done
    printf '\n[NPU devfreq]\n'
    shopt -s nullglob
    npu_nodes=("$SYS_ROOT"/class/devfreq/*.npu)
    shopt -u nullglob
    for node in "${npu_nodes[@]}"; do
        printf '%s\n' "${node##*/}"
        for attribute in cur_freq min_freq max_freq governor available_frequencies; do
            if [[ -r "$node/$attribute" ]]; then
                printf '  %s=' "$attribute"
                tr -d '\000' <"$node/$attribute" 2>/dev/null || true
                printf '\n'
            fi
        done
    done
} >"$REPORT/npu.txt"

{
    printf '[ALSA cards]\n'
    if [[ -r "$PROC_ROOT/asound/cards" ]]; then
        cat "$PROC_ROOT/asound/cards"
    else
        printf 'unavailable\n'
    fi
} >"$REPORT/audio.txt"
append_command audio.txt aplay -l
append_command audio.txt aplay -L

{
    printf 'Interface state only; addresses and MACs are intentionally omitted.\n\n'
    shopt -s nullglob
    interfaces=("$SYS_ROOT"/class/net/*)
    shopt -u nullglob
    for interface in "${interfaces[@]}"; do
        name=${interface##*/}
        [[ "$name" == lo ]] && continue
        printf '[%s]\n' "$name"
        for attribute in operstate carrier speed duplex mtu; do
            if [[ -r "$interface/$attribute" ]]; then
                printf '%s=' "$attribute"
                tr -d '\000' <"$interface/$attribute" 2>/dev/null || true
                printf '\n'
            fi
        done
        printf '\n'
    done
} >"$REPORT/network.txt"

run_capture storage.txt lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,ROTA,TRAN
{
    printf '\n[root filesystem]\n'
    if command -v findmnt >/dev/null 2>&1; then
        findmnt -n -o SOURCE,FSTYPE / 2>&1 | sanitize_stream
    else
        printf 'findmnt unavailable\n'
    fi
} >>"$REPORT/storage.txt"
run_capture usb.txt lsusb -t
append_command usb.txt lspci -nnk

{
    if command -v dpkg-query >/dev/null 2>&1; then
        dpkg-query -W -f='${binary:Package}\t${Version}\n' 2>/dev/null |
            grep -Ei 'armbian|linux-image|linux-dtb|mesa|vulkan|kodi|ffmpeg|mpp|rga|rknn|alsa|libcec' |
            sort || true
    elif command -v rpm >/dev/null 2>&1; then
        rpm -qa 2>/dev/null |
            grep -Ei 'kernel|mesa|vulkan|kodi|ffmpeg|mpp|rga|rknn|alsa|libcec' |
            sort || true
    else
        printf 'supported package inventory tool unavailable\n'
    fi
} >"$REPORT/packages.txt"

kernel_pattern='drm|hdmi|vop|edid|panthor|panfrost|mali|rknpu|npu|rkvdec|rkvenc|av1|mpp|rga|alsa|snd|cec|pcie|nvme|mmc'
{
    printf '[filtered current-boot kernel messages]\n'
    if command -v journalctl >/dev/null 2>&1; then
        timeout 45s journalctl -b -k --no-pager 2>&1 |
            grep -Ei "$kernel_pattern" | tail -2500 || true
    elif command -v dmesg >/dev/null 2>&1; then
        dmesg --color=never 2>&1 |
            grep -Ei "$kernel_pattern" | tail -2500 || true
    else
        printf 'kernel log reader unavailable\n'
    fi
} | sanitize_stream >"$REPORT/kernel-media.log"

if ((DIRECTORY_OUTPUT)); then
    mkdir -- "$OUTPUT"
    cp -a "$REPORT/." "$OUTPUT/"
    chmod -R go-rwx "$OUTPUT"
else
    command -v tar >/dev/null 2>&1 || die "tar is required for archive output"
    tar -C "$REPORT" -czf "$OUTPUT" .
    chmod 0600 "$OUTPUT"
fi

printf 'Diagnostic bundle written to %s\n' "$OUTPUT"
