#!/bin/sh
# ============================================================
# hw_collect.sh -- Hardware inventory collector for hiperiso
#
# Collects comprehensive hardware data across three phases:
#
#   hw_collect.sh pre       Before QEMU: host /proc, /sys, ACPI,
#                           SMBIOS, KVM params, USB, EFI
#   hw_collect.sh monitor   Background: send HMP commands to QEMU
#                           via FIFO pipe chardev (18 commands)
#   hw_collect.sh post       After QEMU: parse raw dump, generate
#                           JSON summaries, extract crash data
#
# Output directory: ${LOG_DIR}/hw/
#
# Host files (always present if pre-phase ran):
#   host_cpuinfo.txt       /proc/cpuinfo
#   host_meminfo.txt       /proc/meminfo
#   host_interrupts.txt    /proc/interrupts
#   host_iomem.txt         /proc/iomem (physical memory map)
#   host_ioports.txt       /proc/ioports
#   host_cmdline.txt       /proc/cmdline
#   host_pci_devices.txt   /proc/bus/pci/devices
#   host_cpu_cache.txt     CPU cache topology from sysfs
#   host_numa.txt          NUMA node topology
#   host_iommu.txt         IOMMU groups (VT-d/AMD-Vi)
#   host_kvm.txt           /dev/kvm presence
#   host_block.txt         Block device info
#   host_dmesg.txt         Host kernel boot messages
#   host_version.txt       /proc/version
#   host_diskstats.txt     /proc/diskstats
#   host_usb.txt           USB device tree from sysfs
#   host_dmi.txt           DMI/SMBIOS identifiers from sysfs
#   acpi/                  Host ACPI tables (binary .aml files)
#   smbios/                Host DMI/SMBIOS raw tables
#   kvm_caps.json          KVM capabilities (module params)
#
# QEMU files (present if QEMU launched and ran >= 3 seconds):
#   qemu_version.txt       QEMU version string
#   qemu_qtree.txt         Full QEMU device tree
#   qemu_pci.txt           PCI device list as seen by guest
#   qemu_memmap.txt        Guest memory map (mtree)
#   qemu_memmap_flat.txt   Flat memory map (mtree -f)
#   qemu_cpuid.txt         CPUID leaves exposed to guest
#   qemu_chardev.txt       QEMU character devices
#   qemu_block.txt         QEMU block devices
#   qemu_net.txt           QEMU network devices
#   qemu_ioapic.txt        IO-APIC interrupt routing
#   qemu_lapic.txt         Local APIC state
#   qemu_registers.txt     CPU register snapshot
#   qemu_tlb.txt           TLB state (TCG only)
#   qemu_numa.txt          Guest NUMA topology
#   qemu_hpet.txt          HPET timer state
#   qemu_irq.txt           IRQ statistics
#   qemu_qomtree.txt       QOM object hierarchy
#   qemu_smbios.txt        SMBIOS table (if QEMU supports it)
#   pci_summary.json       Structured PCI device list
# ============================================================

. /hiperiso-lib.sh

LOG_DIR="${LOG_DIR:?hw_collect: LOG_DIR not set}"
HW_DIR="${LOG_DIR}/hw"
MON_IN="/tmp/hw_mon.in"
MON_OUT="/tmp/hw_mon.out"

mkdir -p "$HW_DIR"

# ── Mode: pre ────────────────────────────────────────────────
hw_collect_pre() {
    hiperiso_log "hw_collect: collecting host hardware inventory..."

    # ── /proc snapshots ──────────────────────────────────────
    cat /proc/cpuinfo   > "$HW_DIR/host_cpuinfo.txt"  2>/dev/null
    cat /proc/meminfo   > "$HW_DIR/host_meminfo.txt"  2>/dev/null
    cat /proc/version   > "$HW_DIR/host_version.txt"  2>/dev/null
    cat /proc/diskstats > "$HW_DIR/host_diskstats.txt" 2>/dev/null

    [ -f /proc/interrupts ] && cat /proc/interrupts > "$HW_DIR/host_interrupts.txt" 2>/dev/null
    [ -f /proc/iomem ]      && cat /proc/iomem      > "$HW_DIR/host_iomem.txt"     2>/dev/null
    [ -f /proc/ioports ]    && cat /proc/ioports     > "$HW_DIR/host_ioports.txt"  2>/dev/null
    [ -f /proc/cmdline ]    && cat /proc/cmdline     > "$HW_DIR/host_cmdline.txt"  2>/dev/null
    [ -f /proc/bus/pci/devices ] && cat /proc/bus/pci/devices > "$HW_DIR/host_pci_devices.txt" 2>/dev/null

    # ── CPU cache topology ───────────────────────────────────
    {
        printf '=== CPU Cache Topology ===\n'
        for _idx in /sys/devices/system/cpu/cpu*/cache/index*/; do
            [ -d "$_idx" ] || continue
            _level=$(cat "${_idx}level" 2>/dev/null)
            _type=$(cat "${_idx}type" 2>/dev/null)
            _size=$(cat "${_idx}size" 2>/dev/null)
            _shared=$(cat "${_idx}shared_cpu_list" 2>/dev/null)
            printf 'cpu=%s level=%s type=%s size=%s shared=%s\n' \
                "$(printf '%s' "$_idx" | sed 's|.*/cpu\(cpu[0-9]*\)/.*|\1|')" \
                "$_level" "$_type" "$_size" "$_shared"
        done
    } > "$HW_DIR/host_cpu_cache.txt" 2>/dev/null

    # ── CPU topology (thread/core/socket IDs) ────────────────
    {
        printf '=== CPU Topology ===\n'
        for _cpu in /sys/devices/system/cpu/cpu[0-9]*; do
            [ -d "$_cpu" ] || continue
            _name=$(basename "$_cpu")
            _core=$(cat "${_cpu}/topology/core_id" 2>/dev/null)
            _sock=$(cat "${_cpu}/topology/physical_package_id" 2>/dev/null)
            _threads=$(cat "${_cpu}/topology/thread_siblings_list" 2>/dev/null)
            printf '%s: core=%s socket=%s siblings=%s\n' \
                "$_name" "${_core:-?}" "${_sock:-?}" "${_threads:-?}"
        done
    } > "$HW_DIR/host_cpu_topology.txt" 2>/dev/null

    # ── NUMA topology ────────────────────────────────────────
    if [ -d /sys/devices/system/node ]; then
        {
            printf '=== NUMA Topology ===\n'
            for _node in /sys/devices/system/node/node[0-9]*; do
                [ -d "$_node" ] || continue
                _name=$(basename "$_node")
                _cpus=$(cat "${_node}/cpulist" 2>/dev/null)
                printf '%s: cpus=%s\n' "$_name" "$_cpus"
            done
        } > "$HW_DIR/host_numa.txt" 2>/dev/null
    fi

    # ── IOMMU groups (VT-d / AMD-Vi) ─────────────────────────
    if [ -d /sys/kernel/iommu_groups ]; then
        {
            printf '=== IOMMU Groups ===\n'
            for _grp in /sys/kernel/iommu_groups/[0-9]*; do
                [ -d "$_grp" ] || continue
                _gid=$(basename "$_grp")
                printf 'group %s:\n' "$_gid"
                for _dev in "$_grp"/devices/*; do
                    [ -e "$_dev" ] || continue
                    printf '  %s\n' "$(basename "$_dev")"
                done
            done
        } > "$HW_DIR/host_iommu.txt" 2>/dev/null
    fi

    # ── KVM presence ─────────────────────────────────────────
    if [ -e /dev/kvm ]; then
        {
            printf '=== KVM Device ===\n'
            ls -la /dev/kvm 2>/dev/null
        } > "$HW_DIR/host_kvm.txt" 2>/dev/null
    fi

    # ── KVM capabilities (module parameters) ─────────────────
    _kvm_json="$HW_DIR/kvm_caps.json"
    {
        printf '{\n'
        printf '  "kvm_present": %s,\n' $([ -e /dev/kvm ] && echo true || echo false)

        # Core KVM parameters
        _first=1
        printf '  "kvm_core": {'
        if [ -d /sys/module/kvm/parameters ]; then
            for _p in /sys/module/kvm/parameters/*; do
                [ -f "$_p" ] || continue
                _pname=$(basename "$_p")
                _pval=$(cat "$_p" 2>/dev/null)
                if [ "$_first" -eq 1 ]; then _first=0; else printf ','; fi
                printf '\n    "%s": "%s"' "$_pname" "$_pval"
            done
        fi
        [ "$_first" -eq 0 ] && printf '\n  '
        printf '},\n'

        # Intel VT-x parameters
        _first=1
        printf '  "kvm_intel": {'
        if [ -d /sys/module/kvm_intel/parameters ]; then
            for _p in /sys/module/kvm_intel/parameters/*; do
                [ -f "$_p" ] || continue
                _pname=$(basename "$_p")
                _pval=$(cat "$_p" 2>/dev/null)
                if [ "$_first" -eq 1 ]; then _first=0; else printf ','; fi
                printf '\n    "%s": "%s"' "$_pname" "$_pval"
            done
        fi
        [ "$_first" -eq 0 ] && printf '\n  '
        printf '},\n'

        # AMD-V parameters
        _first=1
        printf '  "kvm_amd": {'
        if [ -d /sys/module/kvm_amd/parameters ]; then
            for _p in /sys/module/kvm_amd/parameters/*; do
                [ -f "$_p" ] || continue
                _pname=$(basename "$_p")
                _pval=$(cat "$_p" 2>/dev/null)
                if [ "$_first" -eq 1 ]; then _first=0; else printf ','; fi
                printf '\n    "%s": "%s"' "$_pname" "$_pval"
            done
        fi
        [ "$_first" -eq 0 ] && printf '\n  '
        printf '}\n'
        printf '}\n'
    } > "$_kvm_json" 2>/dev/null

    # ── Block devices ────────────────────────────────────────
    {
        printf '=== Block Devices ===\n'
        for _blk in /sys/block/sd* /sys/block/nvme* /sys/block/sr* /sys/block/mmcblk*; do
            [ -d "$_blk" ] || continue
            _name=$(basename "$_blk")
            _size=$(cat "${_blk}/size" 2>/dev/null)
            _rm=$(cat "${_blk}/removable" 2>/dev/null)
            _ro=$(cat "${_blk}/readonly" 2>/dev/null)
            _model=$(cat "${_blk}/device/model" 2>/dev/null)
            _vendor=$(cat "${_blk}/device/vendor" 2>/dev/null)
            printf '%s: sectors=%s removable=%s readonly=%s model=%s vendor=%s\n' \
                "$_name" "${_size:-?}" "${_rm:-?}" "${_ro:-?}" \
                "$(printf '%s' "$_model" | tr -d ' ')" \
                "$(printf '%s' "$_vendor" | tr -d ' ')"
        done
    } > "$HW_DIR/host_block.txt" 2>/dev/null

    # ── USB device tree ──────────────────────────────────────
    {
        printf '=== USB Devices ===\n'
        for _dev in /sys/bus/usb/devices/*; do
            [ -d "$_dev" ] || continue
            _name=$(basename "$_dev")
            _vid=$(cat "${_dev}/idVendor" 2>/dev/null)
            _pid=$(cat "${_dev}/idProduct" 2>/dev/null)
            _mfr=$(cat "${_dev}/manufacturer" 2>/dev/null)
            _prod=$(cat "${_dev}/product" 2>/dev/null)
            _speed=$(cat "${_dev}/speed" 2>/dev/null)
            [ -n "$_vid" ] || continue
            printf '%s: %s:%s speed=%s mfr=%s product=%s\n' \
                "$_name" "$_vid" "$_pid" "${_speed:-?}" \
                "$(printf '%s' "$_mfr" | tr -d '\n')" \
                "$(printf '%s' "$_prod" | tr -d '\n')"
        done
    } > "$HW_DIR/host_usb.txt" 2>/dev/null

    # ── ACPI tables (host firmware) ──────────────────────────
    if [ -d /sys/firmware/acpi/tables ]; then
        mkdir -p "$HW_DIR/acpi"
        cp -r /sys/firmware/acpi/tables/* "$HW_DIR/acpi/" 2>/dev/null
        {
            printf '=== ACPI Tables ===\n'
            for _t in /sys/firmware/acpi/tables/[A-Z]*; do
                [ -f "$_t" ] || continue
                _sig=$(basename "$_t")
                _size=$(wc -c < "$_t" 2>/dev/null)
                printf '%s: %s bytes\n' "$_sig" "${_size:-?}"
            done
        } > "$HW_DIR/acpi/_summary.txt" 2>/dev/null
    fi

    # ── DMI / SMBIOS (host firmware) ─────────────────────────
    if [ -d /sys/firmware/dmi/tables ]; then
        mkdir -p "$HW_DIR/smbios"
        cp /sys/firmware/dmi/tables/* "$HW_DIR/smbios/" 2>/dev/null
    fi
    if [ -d /sys/class/dmi/id ]; then
        {
            printf '=== DMI Identifiers ===\n'
            for _f in /sys/class/dmi/id/*; do
                [ -f "$_f" ] || continue
                _key=$(basename "$_f")
                _val=$(cat "$_f" 2>/dev/null)
                [ -n "$_val" ] && printf '%s: %s\n' "$_key" "$_val"
            done
        } > "$HW_DIR/host_dmi.txt" 2>/dev/null
    fi

    # ── EFI firmware info (if UEFI boot) ─────────────────────
    if [ -d /sys/firmware/efi ]; then
        {
            printf '=== EFI Firmware ===\n'
            cat /sys/firmware/efi/fw_platform_size 2>/dev/null
            printf 'Runtime services: '
            ls /sys/firmware/efi/runtime 2>/dev/null || printf '(not exported)\n'
            printf 'EFI variables:\n'
            ls /sys/firmware/efi/efivars/ 2>/dev/null | head -20
        } > "$HW_DIR/host_efi.txt" 2>/dev/null
    fi

    # ── Kernel config (if available) ─────────────────────────
    if [ -f /proc/config.gz ]; then
        cp /proc/config.gz "$HW_DIR/host_kernel_config.gz" 2>/dev/null
    fi

    # ── DMESG (host kernel messages) ─────────────────────────
    if command -v dmesg >/dev/null 2>&1; then
        dmesg > "$HW_DIR/host_dmesg.txt" 2>/dev/null
    fi

    _count=$(ls "$HW_DIR"/host_*.txt "$HW_DIR"/*.json 2>/dev/null | wc -l)
    hiperiso_log "hw_collect: host inventory complete ($_count files)"
}

# ── Mode: monitor ────────────────────────────────────────────
hw_setup_monitor_fifo() {
    mkfifo "$MON_IN" "$MON_OUT" 2>/dev/null

    cat "$MON_OUT" > "${HW_DIR}/qemu_monitor_raw.txt" 2>/dev/null &
    MON_READER_PID=$!
    printf '%s\n' "$MON_READER_PID" > /tmp/hw_mon_reader.pid

    # Writer: sends HMP commands via echo-delimited markers.
    # QEMU pipe mode does not echo input, so we inject markers
    # using QEMU's HMP 'echo' command to enable section splitting.
    (
        sleep 3

        # ── P0: Core inventory ───────────────────────────────
        printf 'echo ===SECTION===version\n';    sleep 0.1
        printf 'info version\n';                  sleep 0.3
        printf 'echo ===SECTION===qtree\n';       sleep 0.1
        printf 'info qtree\n';                    sleep 0.5
        printf 'echo ===SECTION===pci\n';         sleep 0.1
        printf 'info pci\n';                      sleep 0.3

        # ── P1: CPU + memory map ─────────────────────────────
        printf 'echo ===SECTION===cpuid\n';       sleep 0.1
        printf 'info cpuid\n';                    sleep 0.3
        printf 'echo ===SECTION===memmap\n';      sleep 0.1
        printf 'info mtree\n';                    sleep 0.3
        printf 'echo ===SECTION===memmap_flat\n'; sleep 0.1
        printf 'info mtree -f\n';                 sleep 0.3

        # ── P2: SMBIOS + registers + TLB ─────────────────────
        printf 'echo ===SECTION===smbios\n';      sleep 0.1
        printf 'info smbios\n';                   sleep 0.3
        printf 'echo ===SECTION===registers\n';   sleep 0.1
        printf 'info registers\n';                sleep 0.3
        printf 'echo ===SECTION===tlb\n';         sleep 0.1
        printf 'info tlb\n';                      sleep 0.3

        # ── P3: Interrupt routing + KVM caps ─────────────────
        printf 'echo ===SECTION===ioapic\n';      sleep 0.1
        printf 'info ioapic\n';                   sleep 0.3
        printf 'echo ===SECTION===lapic\n';       sleep 0.1
        printf 'info lapic\n';                    sleep 0.3
        printf 'echo ===SECTION===irq\n';         sleep 0.1
        printf 'info irq\n';                      sleep 0.3

        # ── Additional: topology + timers ────────────────────
        printf 'echo ===SECTION===numa\n';        sleep 0.1
        printf 'info numa\n';                     sleep 0.3
        printf 'echo ===SECTION===hpet\n';        sleep 0.1
        printf 'info hpet\n';                     sleep 0.3
        printf 'echo ===SECTION===qomtree\n';     sleep 0.1
        printf 'info qom-tree\n';                 sleep 0.3

        # ── Device summaries ─────────────────────────────────
        printf 'echo ===SECTION===chardev\n';     sleep 0.1
        printf 'info chardev\n';                  sleep 0.3
        printf 'echo ===SECTION===block\n';       sleep 0.1
        printf 'info block\n';                    sleep 0.3
        printf 'echo ===SECTION===net\n';         sleep 0.1
        printf 'info networking\n';               sleep 0.3
    ) > "$MON_IN" 2>/dev/null &
    MON_WRITER_PID=$!
    printf '%s\n' "$MON_WRITER_PID" > /tmp/hw_mon_writer.pid

    hiperiso_log "hw_collect: monitor FIFO reader (PID $MON_READER_PID)"
    hiperiso_log "hw_collect: monitor FIFO writer (PID $MON_WRITER_PID)"
}

# ── Mode: post ───────────────────────────────────────────────
hw_collect_post() {
    _raw="${HW_DIR}/qemu_monitor_raw.txt"

    if [ ! -f "$_raw" ] || [ ! -s "$_raw" ]; then
        hiperiso_log "hw_collect: no QEMU monitor data (raw file missing or empty)"
        hw_cleanup_procs
        return 1
    fi

    hiperiso_log "hw_collect: parsing QEMU monitor output..."

    # Split raw dump into per-section files using echo markers
    # injected by the writer. Each ===SECTION===xxx line triggers
    # a section change; subsequent output goes to that file.
    _awk_script='
        BEGIN { section = "header"; }
        /===SECTION===version/     { section = "version";     next; }
        /===SECTION===qtree/       { section = "qtree";       next; }
        /===SECTION===pci/         { section = "pci";          next; }
        /===SECTION===cpuid/       { section = "cpuid";       next; }
        /===SECTION===memmap_flat/ { section = "memmap_flat"; next; }
        /===SECTION===memmap/      { section = "memmap";      next; }
        /===SECTION===smbios/      { section = "smbios";      next; }
        /===SECTION===registers/   { section = "registers";   next; }
        /===SECTION===tlb/         { section = "tlb";         next; }
        /===SECTION===ioapic/      { section = "ioapic";      next; }
        /===SECTION===lapic/       { section = "lapic";       next; }
        /===SECTION===irq/         { section = "irq";         next; }
        /===SECTION===numa/        { section = "numa";        next; }
        /===SECTION===hpet/        { section = "hpet";        next; }
        /===SECTION===qomtree/     { section = "qomtree";     next; }
        /===SECTION===chardev/     { section = "chardev";     next; }
        /===SECTION===block/       { section = "block";       next; }
        /===SECTION===net/         { section = "net";         next; }
        {
            if (section != "header")
                print > "'"$HW_DIR"'/qemu_" section ".txt";
        }
    '

    awk "$_awk_script" "$_raw" 2>/dev/null

    for _f in \
        qemu_version qemu_qtree qemu_pci qemu_cpuid \
        qemu_memmap qemu_memmap_flat qemu_smbios \
        qemu_registers qemu_tlb qemu_ioapic qemu_lapic \
        qemu_irq qemu_numa qemu_hpet qemu_qomtree \
        qemu_chardev qemu_block qemu_net; do
        _fp="${HW_DIR}/${_f}.txt"
        if [ -f "$_fp" ] && [ ! -s "$_fp" ]; then
            rm -f "$_fp"
        fi
    done

    # Generate PCI summary JSON from qemu_pci.txt
    hw_gen_pci_json

    _count=$(ls "$HW_DIR"/qemu_*.txt "$HW_DIR"/*.json 2>/dev/null | wc -l)
    hiperiso_log "hw_collect: parsed $_count QEMU + JSON files"

    hw_cleanup_procs

    hiperiso_log "hw_collect: inventory files:"
    ls -la "$HW_DIR"/ 2>/dev/null >> "${LOG_DIR}/env.txt"
}

# ── PCI summary JSON ─────────────────────────────────────────
# Parse qemu_pci.txt to extract structured PCI device data.
hw_gen_pci_json() {
    _pci="${HW_DIR}/qemu_pci.txt"
    _out="${HW_DIR}/pci_summary.json"
    [ -f "$_pci" ] || return 0

    {
        printf '{\n'
        printf '  "devices": [\n'

        awk '
            # Device header: "  Bus  0, device   1, function 0:"
            # (QEMU outputs 2 leading spaces — no ^ anchor)
            /Bus .*device .*function/ {
                if (found) printf " },\n"
                found = 1
                bus = $2; gsub(/,/, "", bus)
                dev = $4; gsub(/,/, "", dev)
                fn = $6; gsub(/:/, "", fn)
                printf "    {\"bus\": %s, \"device\": %s, \"function\": %s", bus, dev, fn
                next
            }
            # Vendor/device line: "    SATA controller: PCI device 8086:2922"
            # or:                   "    Class 0106: PCI device 8086:2922"
            /PCI device/ {
                for (i = 1; i <= NF; i++) {
                    if (index($i, ":") > 0) {
                        split($i, vd, ":")
                        if (length(vd[1]) == 4 && length(vd[2]) == 4 && \
                            vd[1] ~ /^[0-9a-fA-F]+$/ && vd[2] ~ /^[0-9a-fA-F]+$/) {
                            printf ", \"vendor_id\": \"0x%s\", \"device_id\": \"0x%s\"", vd[1], vd[2]
                            break
                        }
                    }
                }
                next
            }
            # IRQ line: "      IRQ 5, pin A"
            /IRQ [0-9]/ {
                _irq = $2; gsub(/,/, "", _irq)
                printf ", \"irq\": \"%s\"", _irq
                next
            }
            END { if (found) printf " }"; printf "\n" }
        ' "$_pci" 2>/dev/null

        printf '  ]\n'
        printf '}\n'
    } > "$_out" 2>/dev/null

    if [ ! -s "$_out" ]; then
        rm -f "$_out"
    fi
}

# ── Process cleanup ──────────────────────────────────────────
hw_cleanup_procs() {
    rm -f "$MON_IN" "$MON_OUT" 2>/dev/null
    for _pidfile in /tmp/hw_mon_reader.pid /tmp/hw_mon_writer.pid; do
        if [ -f "$_pidfile" ]; then
            _pid=$(cat "$_pidfile" 2>/dev/null)
            if [ -n "$_pid" ] && kill -0 "$_pid" 2>/dev/null; then
                kill "$_pid" 2>/dev/null
            fi
            rm -f "$_pidfile"
        fi
    done
}

# ── Main ─────────────────────────────────────────────────────
case "${1:-}" in
    pre)     hw_collect_pre ;;
    monitor) hw_setup_monitor_fifo ;;
    post)    hw_collect_post ;;
    *)
        printf 'Usage: hw_collect.sh {pre|monitor|post}\n' >&2
        exit 1
        ;;
esac
