#!/usr/bin/env bash
# scripts/manual/run_hardware_media_smoke.sh
# Manual: local/remote hardware media smoke evidence; not part of CI.
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
STAMP="$(date +%Y%m%d-%H%M%S)"
ARTIFACT_DIR="${LESAVKA_HARDWARE_SMOKE_DIR:-/tmp/lesavka-hardware-media-smoke-${STAMP}}"
RESULTS_TSV="${ARTIFACT_DIR}/results.tsv"
SUMMARY_JSON="${ARTIFACT_DIR}/summary.json"
SUMMARY_TXT="${ARTIFACT_DIR}/summary.txt"

UPSTREAM_HEVC_FILE="${ARTIFACT_DIR}/upstream-hevc-hardware.hevc"
UPSTREAM_HEVC_FRAME="${ARTIFACT_DIR}/upstream-hevc-hardware-frame.png"
DOWNSTREAM_H264_FILE="${ARTIFACT_DIR}/downstream-h264-hardware.h264"
DOWNSTREAM_H264_FRAME="${ARTIFACT_DIR}/downstream-h264-hardware-frame.png"
AUDIO_WAV="${ARTIFACT_DIR}/audio-aac-roundtrip.wav"
AUDIO_RMS_JSON="${ARTIFACT_DIR}/audio-aac-roundtrip-rms.json"
SMOKE_WIDTH="${LESAVKA_HARDWARE_SMOKE_WIDTH:-1280}"
SMOKE_HEIGHT="${LESAVKA_HARDWARE_SMOKE_HEIGHT:-720}"
SMOKE_FPS="${LESAVKA_HARDWARE_SMOKE_FPS:-30}"
SMOKE_FRAMES="${LESAVKA_HARDWARE_SMOKE_FRAMES:-90}"
SMOKE_BITRATE_KBPS="${LESAVKA_HARDWARE_SMOKE_BITRATE_KBPS:-5000}"

mkdir -p "${ARTIFACT_DIR}"
: >"${RESULTS_TSV}"

OVERALL=0

sanitize() {
  printf '%s' "$*" | tr '\t\r\n' '   '
}

record_result() {
  local name="$1"
  local status="$2"
  local detail="$3"
  local artifact="${4:-}"
  printf '%s\t%s\t%s\t%s\n' \
    "${name}" \
    "${status}" \
    "$(sanitize "${detail}")" \
    "${artifact}" >>"${RESULTS_TSV}"
}

have_command() {
  command -v "$1" >/dev/null 2>&1
}

gst_has() {
  gst-inspect-1.0 "$1" >/dev/null 2>&1
}

ffmpeg_has_encoder() {
  local codec="$1"
  ffmpeg -hide_banner -encoders 2>/dev/null \
    | awk -v codec="${codec}" '$2 == codec { found = 1 } END { exit found ? 0 : 1 }'
}

ffmpeg_nvenc_usable() {
  local encoder="$1"
  [[ -e /dev/nvidiactl ]] || return 1
  ffmpeg -hide_banner -loglevel error -nostdin \
    -f lavfi -i color=c=black:s=128x128:r=1 \
    -frames:v 1 -an -pix_fmt nv12 -c:v "${encoder}" -f null - \
    >/dev/null 2>&1
}

run_logged() {
  local name="$1"
  local detail="$2"
  local rc=0
  shift 2
  local log="${ARTIFACT_DIR}/${name}.log"

  echo "==> ${name}"
  if "$@" >"${log}" 2>&1; then
    record_result "${name}" "pass" "${detail}" "${log}"
    echo "   pass"
    return 0
  else
    rc=$?
  fi

  record_result "${name}" "fail" "${detail}; exit=${rc}" "${log}"
  echo "   fail (exit ${rc})"
  sed -n '1,120p' "${log}" | sed 's/^/   | /'
  OVERALL=1
  return 0
}

run_shell() {
  local name="$1"
  local detail="$2"
  local command_text="$3"
  run_logged "${name}" "${detail}" bash -o pipefail -c "${command_text}"
}

finish_summary() {
  python3 - "${RESULTS_TSV}" "${SUMMARY_JSON}" "${SUMMARY_TXT}" "${ARTIFACT_DIR}" \
    "${UPSTREAM_HEVC_FILE}" "${UPSTREAM_HEVC_FRAME}" \
    "${DOWNSTREAM_H264_FILE}" "${DOWNSTREAM_H264_FRAME}" \
    "${AUDIO_WAV}" "${AUDIO_RMS_JSON}" <<'PY'
import json
import pathlib
import sys

results_tsv = pathlib.Path(sys.argv[1])
summary_json = pathlib.Path(sys.argv[2])
summary_txt = pathlib.Path(sys.argv[3])
artifact_dir = pathlib.Path(sys.argv[4])
artifact_paths = {
    "upstream_hevc_stream": sys.argv[5],
    "upstream_hevc_hardware_frame": sys.argv[6],
    "downstream_h264_stream": sys.argv[7],
    "downstream_h264_hardware_frame": sys.argv[8],
    "audio_aac_roundtrip_wav": sys.argv[9],
    "audio_aac_roundtrip_rms": sys.argv[10],
}

results = []
for line in results_tsv.read_text(encoding="utf-8").splitlines():
    name, status, detail, artifact = (line.split("\t") + ["", "", "", ""])[:4]
    results.append(
        {
            "name": name,
            "status": status,
            "detail": detail,
            "artifact": artifact,
        }
    )

failed = [row for row in results if row["status"] == "fail"]
summary = {
    "status": "fail" if failed else "pass",
    "artifact_dir": str(artifact_dir),
    "results": results,
    "artifacts": artifact_paths,
}
summary_json.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8")

lines = [
    "Lesavka hardware media smoke summary",
    f"status: {summary['status']}",
    f"artifact_dir: {artifact_dir}",
    "",
]
for row in results:
    artifact = f" ({row['artifact']})" if row["artifact"] else ""
    lines.append(f"- {row['status']}: {row['name']} - {row['detail']}{artifact}")
lines.extend(["", "Inspectable artifacts:"])
for name, path in artifact_paths.items():
    lines.append(f"- {name}: {path}")
summary_txt.write_text("\n".join(lines) + "\n", encoding="utf-8")
PY

  echo "==> hardware media smoke summary"
  sed -n '1,160p' "${SUMMARY_TXT}"
  echo "summary_json: ${SUMMARY_JSON}"
  echo "summary_txt: ${SUMMARY_TXT}"
}

require_prereqs() {
  local missing=()
  for cmd in ffmpeg gst-inspect-1.0 gst-launch-1.0 python3 awk grep; do
    if ! have_command "${cmd}"; then
      missing+=("${cmd}")
    fi
  done

  if ((${#missing[@]})); then
    record_result "prerequisites" "fail" "missing commands: ${missing[*]}" ""
    OVERALL=1
    return 1
  fi

  record_result "prerequisites" "pass" "ffmpeg, GStreamer, and Python are available" ""
  return 0
}

select_gst_h264_decoder() {
  local decoder
  for decoder in nvh264dec nvh264sldec vah264dec vaapih264dec vaapi264dec v4l2h264dec v4l2slh264dec; do
    if gst_has "${decoder}"; then
      printf '%s\n' "${decoder}"
      return 0
    fi
  done
  if [[ ${LESAVKA_ALLOW_VULKAN_H264_DECODER:-0} == 1 ]] && gst_has vulkanh264dec; then
    printf '%s\n' vulkanh264dec
    return 0
  fi
  return 1
}

select_gst_hevc_decoder() {
  local decoder
  for decoder in nvh265dec vah265dec vaapih265dec v4l2h265dec v4l2slh265dec; do
    if gst_has "${decoder}"; then
      printf '%s\n' "${decoder}"
      return 0
    fi
  done
  return 1
}

select_gst_hevc_encoder() {
  local encoder
  for encoder in nvh265enc nvautogpuh265enc vah265enc vaapih265enc v4l2h265enc; do
    if gst_has "${encoder}"; then
      printf '%s\n' "${encoder}"
      return 0
    fi
  done
  return 1
}

select_gst_h264_encoder() {
  local encoder
  for encoder in nvh264enc nvautogpuh264enc vah264enc vaapih264enc v4l2h264enc; do
    if gst_has "${encoder}"; then
      printf '%s\n' "${encoder}"
      return 0
    fi
  done
  return 1
}

gst_h264_decode_chain() {
  case "$1" in
    vulkanh264dec)
      printf '%s\n' 'vulkanh264dec discard-corrupted-frames=true automatic-request-sync-points=true ! vulkandownload'
      ;;
    nvh264sldec|nvh264dec|vah264dec|vaapih264dec|vaapi264dec|v4l2h264dec|v4l2slh264dec)
      printf '%s\n' "$1"
      ;;
    *)
      return 1
      ;;
  esac
}

gst_hevc_decode_chain() {
  case "$1" in
    nvh265dec|vah265dec|vaapih265dec|v4l2h265dec|v4l2slh265dec)
      printf '%s\n' "$1"
      ;;
    *)
      return 1
      ;;
  esac
}

select_gst_aac_encoder() {
  local encoder
  for encoder in fdkaacenc voaacenc avenc_aac; do
    if gst_has "${encoder}"; then
      printf '%s\n' "${encoder}"
      return 0
    fi
  done
  return 1
}

main() {
  echo "==> Lesavka hardware media smoke"
  echo "   artifact_dir=${ARTIFACT_DIR}"
  echo "   video=${SMOKE_WIDTH}x${SMOKE_HEIGHT}@${SMOKE_FPS} frames=${SMOKE_FRAMES}"
  echo "   no sudo, no systemctl, no UVC gadget reset, and no display-manager reset are used"

  if ! require_prereqs; then
    finish_summary
    exit 1
  fi

  local hevc_encoder=""
  local hevc_encoder_route=""
  local h264_encoder=""
  local h264_encoder_route=""
  local gst_hevc_decoder=""
  local gst_hevc_chain=""
  local gst_h264_decoder=""
  local gst_h264_chain=""

  if ffmpeg_has_encoder hevc_nvenc && ffmpeg_nvenc_usable hevc_nvenc; then
    hevc_encoder="hevc_nvenc"
    hevc_encoder_route="ffmpeg"
  elif hevc_encoder="$(select_gst_hevc_encoder)"; then
    hevc_encoder_route="gstreamer"
  else
    record_result "client_upstream_hevc_hardware_encoder" "fail" "no runtime-usable HEVC hardware encoder found" ""
    OVERALL=1
  fi
  if [[ -n ${hevc_encoder} ]]; then
    record_result "client_upstream_hevc_hardware_encoder" "pass" "selected ${hevc_encoder_route}:${hevc_encoder}" ""
  fi

  if ffmpeg_has_encoder h264_nvenc && ffmpeg_nvenc_usable h264_nvenc; then
    h264_encoder="h264_nvenc"
    h264_encoder_route="ffmpeg"
  elif h264_encoder="$(select_gst_h264_encoder)"; then
    h264_encoder_route="gstreamer"
  else
    record_result "client_downstream_h264_hardware_source" "fail" "no runtime-usable H.264 hardware encoder found" ""
    OVERALL=1
  fi
  if [[ -n ${h264_encoder} ]]; then
    record_result "client_downstream_h264_hardware_source" "pass" "selected ${h264_encoder_route}:${h264_encoder}" ""
  fi

  if gst_hevc_decoder="$(select_gst_hevc_decoder)"; then
    gst_hevc_chain="$(gst_hevc_decode_chain "${gst_hevc_decoder}")"
    record_result "client_upstream_gstreamer_hevc_hw_decoder_available" "pass" "selected ${gst_hevc_decoder}" ""
  else
    record_result "client_upstream_gstreamer_hevc_hw_decoder_available" "fail" "no GStreamer hardware H.265 decoder found" ""
    OVERALL=1
  fi

  if gst_h264_decoder="$(select_gst_h264_decoder)"; then
    gst_h264_chain="$(gst_h264_decode_chain "${gst_h264_decoder}")"
    record_result "client_downstream_gstreamer_h264_hw_decoder_available" "pass" "selected ${gst_h264_decoder}" ""
  else
    record_result "client_downstream_gstreamer_h264_hw_decoder_available" "fail" "no GStreamer hardware H.264 decoder found" ""
    OVERALL=1
  fi

  local aac_encoder=""
  if aac_encoder="$(select_gst_aac_encoder)" && gst_has avdec_aac && gst_has wavenc; then
    record_result "audio_roundtrip_elements_available" "pass" "selected ${aac_encoder} -> avdec_aac -> wavenc" ""
  else
    record_result "audio_roundtrip_elements_available" "fail" "AAC encoder/decoder/wavenc elements are missing" ""
    OVERALL=1
  fi

  if [[ ${OVERALL} -eq 0 ]]; then
    if [[ ${hevc_encoder_route} == ffmpeg ]]; then
      run_shell "client_upstream_hevc_hardware_file" \
        "GPU encodes a synthetic upstream HEVC stream with ${hevc_encoder}" \
        "ffmpeg -hide_banner -loglevel warning -y -nostdin -f lavfi -i testsrc2=size=${SMOKE_WIDTH}x${SMOKE_HEIGHT}:rate=${SMOKE_FPS} -frames:v ${SMOKE_FRAMES} -an -sn -dn -vf format=nv12 -c:v ${hevc_encoder} -preset p1 -tune ll -rc cbr -b:v ${SMOKE_BITRATE_KBPS}k -maxrate ${SMOKE_BITRATE_KBPS}k -bufsize ${SMOKE_BITRATE_KBPS}k -g ${SMOKE_FPS} -bf 0 -forced-idr 1 -f hevc '${UPSTREAM_HEVC_FILE}'"
    else
      run_shell "client_upstream_hevc_hardware_file" \
        "GPU encodes a synthetic upstream HEVC stream with ${hevc_encoder}" \
        "gst-launch-1.0 -q videotestsrc num-buffers=${SMOKE_FRAMES} is-live=false pattern=smpte ! video/x-raw,width=${SMOKE_WIDTH},height=${SMOKE_HEIGHT},framerate=${SMOKE_FPS}/1 ! videoconvert ! video/x-raw,format=NV12 ! ${hevc_encoder} ! h265parse config-interval=-1 ! video/x-h265,stream-format=byte-stream,alignment=au ! filesink location='${UPSTREAM_HEVC_FILE}'"
    fi

    run_shell "client_upstream_hevc_gstreamer_parse" \
      "GStreamer accepts the HEVC elementary stream shape used by the upstream bundle path" \
      "gst-launch-1.0 -q filesrc location='${UPSTREAM_HEVC_FILE}' ! h265parse config-interval=-1 ! video/x-h265,stream-format=byte-stream,alignment=au ! fakesink sync=false"

    run_shell "client_upstream_hevc_hardware_frame" \
      "GStreamer hardware decoder ${gst_hevc_decoder} emits one visual HEVC proof frame" \
      "gst-launch-1.0 -q filesrc location='${UPSTREAM_HEVC_FILE}' ! h265parse ! ${gst_hevc_chain} ! videoconvert ! pngenc snapshot=true ! filesink location='${UPSTREAM_HEVC_FRAME}'"

    if [[ ${h264_encoder_route} == ffmpeg ]]; then
      run_shell "client_downstream_h264_hardware_file" \
        "GPU creates a downstream-like H.264 elementary stream with ${h264_encoder}" \
        "ffmpeg -hide_banner -loglevel warning -y -nostdin -f lavfi -i testsrc2=size=${SMOKE_WIDTH}x${SMOKE_HEIGHT}:rate=${SMOKE_FPS} -frames:v ${SMOKE_FRAMES} -an -sn -dn -vf format=nv12 -c:v ${h264_encoder} -preset p1 -tune ll -rc cbr -b:v ${SMOKE_BITRATE_KBPS}k -maxrate ${SMOKE_BITRATE_KBPS}k -bufsize ${SMOKE_BITRATE_KBPS}k -g ${SMOKE_FPS} -bf 0 -forced-idr 1 -f h264 '${DOWNSTREAM_H264_FILE}'"
    else
      run_shell "client_downstream_h264_hardware_file" \
        "GPU creates a downstream-like H.264 elementary stream with ${h264_encoder}" \
        "gst-launch-1.0 -q videotestsrc num-buffers=${SMOKE_FRAMES} is-live=false pattern=smpte ! video/x-raw,width=${SMOKE_WIDTH},height=${SMOKE_HEIGHT},framerate=${SMOKE_FPS}/1 ! videoconvert ! video/x-raw,format=NV12 ! ${h264_encoder} ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,alignment=au ! filesink location='${DOWNSTREAM_H264_FILE}'"
    fi

    run_shell "client_downstream_h264_gstreamer_hw_decode" \
      "GStreamer decodes H.264 with hardware decoder ${gst_h264_decoder}" \
      "gst-launch-1.0 -q filesrc location='${DOWNSTREAM_H264_FILE}' ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,alignment=au ! ${gst_h264_chain} ! videoconvert ! fakesink sync=false"

    run_shell "client_downstream_h264_hardware_frame" \
      "GStreamer hardware decoder ${gst_h264_decoder} emits one visual H.264 proof frame" \
      "gst-launch-1.0 -q filesrc location='${DOWNSTREAM_H264_FILE}' ! h264parse ! ${gst_h264_chain} ! videoconvert ! pngenc snapshot=true ! filesink location='${DOWNSTREAM_H264_FRAME}'"

    run_shell "audio_aac_roundtrip_wav" \
      "GStreamer encodes and decodes a 1 kHz tone to a WAV artifact for audio-path sanity" \
      "gst-launch-1.0 -q audiotestsrc wave=sine freq=1000 num-buffers=120 ! audio/x-raw,format=S16LE,channels=2,rate=48000 ! audioconvert ! audioresample ! ${aac_encoder} ! aacparse ! avdec_aac ! audioconvert ! audioresample ! audio/x-raw,format=S16LE,channels=2,rate=48000 ! wavenc ! filesink location='${AUDIO_WAV}'"

    run_shell "audio_aac_roundtrip_rms" \
      "Decoded WAV contains non-silent audio energy" \
      "python3 - '${AUDIO_WAV}' '${AUDIO_RMS_JSON}' <<'PY'
import json
import math
import pathlib
import struct
import sys
import wave

wav_path = pathlib.Path(sys.argv[1])
out_path = pathlib.Path(sys.argv[2])
with wave.open(str(wav_path), 'rb') as wav:
    frames = wav.readframes(wav.getnframes())
    sample_count = len(frames) // 2
    samples = struct.unpack('<' + 'h' * sample_count, frames)
    rms = math.sqrt(sum(sample * sample for sample in samples) / max(sample_count, 1))
summary = {'rms': rms, 'sample_count': sample_count, 'wav': str(wav_path)}
out_path.write_text(json.dumps(summary, indent=2, sort_keys=True) + '\\n', encoding='utf-8')
if rms < 500:
    raise SystemExit(f'audio RMS too low: {rms:.2f}')
print(json.dumps(summary, sort_keys=True))
PY"
  fi

  if [[ ${LESAVKA_RUN_REMOTE_MEDIA_SMOKE:-0} == 1 ]]; then
    local remote_host="${LESAVKA_SERVER_HOST:-titan-jh}"
    local ssh_opts="${SSH_OPTS:--o BatchMode=yes -o ConnectTimeout=5}"
    if ssh ${ssh_opts} "${remote_host}" "gst-inspect-1.0 v4l2slh265dec >/dev/null"; then
      run_shell "server_hevc_hardware_decode" \
        "Remote server decodes the same upstream HEVC stream with v4l2slh265dec; no service/gadget mutation" \
        "cat '${UPSTREAM_HEVC_FILE}' | ssh ${ssh_opts} '${remote_host}' \"gst-launch-1.0 -q fdsrc blocksize=1048576 ! h265parse disable-passthrough=true config-interval=-1 ! video/x-h265,stream-format=byte-stream,alignment=au ! v4l2slh265dec discard-corrupted-frames=true automatic-request-sync-points=true ! videoconvert ! fakesink sync=false\""
    else
      record_result "server_hevc_hardware_decode" "fail" "remote ${remote_host} did not expose v4l2slh265dec over ssh" ""
      OVERALL=1
    fi
  else
    record_result "server_hevc_hardware_decode" "skipped" "set LESAVKA_RUN_REMOTE_MEDIA_SMOKE=1 to verify Theia's hardware HEVC decoder without touching services" ""
  fi

  finish_summary
  exit "${OVERALL}"
}

main "$@"
