#!/bin/sh

# compute_image_summary() - Sets the global IMAGE_SUMMARY to a one-line
# human-readable description of the image referenced by GOLD_MASTER_OS_FILE.
#
# This must be called after read_config() so GOLD_MASTER_OS_FILE is in scope.
# The result is what the device-details page shows in the "Image" column,
# both during provisioning (via state.db) and after (via manufacturing.db).
#
# Behaviour:
#   - regular file:    basename (e.g. "raspios-2025-04-01.img")
#   - IDP directory:   "<name> v<version> (<class>/<storage>[, encrypted])"
#                      built from the descriptor JSON's IGmeta/attributes
#   - unset / missing: empty string
compute_image_summary() {
    IMAGE_SUMMARY=""

    src="${GOLD_MASTER_OS_FILE:-}"
    if [ -z "${src}" ]; then
        return 0
    fi

    if [ -f "${src}" ]; then
        IMAGE_SUMMARY="$(basename -- "${src}")"
        return 0
    fi

    if [ ! -d "${src}" ]; then
        return 0
    fi

    # IDP artefact directory.  Locate the single descriptor JSON and pull
    # name/version/class/storage/encryption out of it.
    idp_json=""
    if command -v jq >/dev/null 2>&1; then
        idp_json="$(find "${src}" -maxdepth 1 -name '*.json' -type f 2>/dev/null | head -n1)"
    fi
    if [ -z "${idp_json}" ] || [ ! -f "${idp_json}" ]; then
        IMAGE_SUMMARY="$(basename -- "${src}")"
        return 0
    fi

    name=$(jq -r '.attributes."image-name" // ""' < "${idp_json}" 2>/dev/null)
    version=$(jq -r '.IGmeta.IGconf_image_version // ""' < "${idp_json}" 2>/dev/null)
    class=$(jq -r '.IGmeta.IGconf_device_class // ""' < "${idp_json}" 2>/dev/null)
    storage=$(jq -r '.IGmeta.IGconf_device_storage_type // ""' < "${idp_json}" 2>/dev/null)
    encrypted=$(jq -r 'if any(.. | objects; has("encrypted")) then "encrypted" else "" end' < "${idp_json}" 2>/dev/null)

    summary="${name}"
    if [ -z "${summary}" ] || [ "${summary}" = "unknown" ]; then
        summary="$(basename -- "${src}")"
    fi
    if [ -n "${version}" ] && [ "${version}" != "unknown" ]; then
        summary="${summary} v${version}"
    fi

    qualifiers=""
    if [ -n "${class}" ] && [ "${class}" != "unknown" ]; then
        qualifiers="${class}"
    fi
    if [ -n "${storage}" ] && [ "${storage}" != "unknown" ]; then
        if [ -n "${qualifiers}" ]; then
            qualifiers="${qualifiers}/${storage}"
        else
            qualifiers="${storage}"
        fi
    fi
    if [ -n "${encrypted}" ]; then
        if [ -n "${qualifiers}" ]; then
            qualifiers="${qualifiers}, ${encrypted}"
        else
            qualifiers="${encrypted}"
        fi
    fi
    if [ -n "${qualifiers}" ]; then
        summary="${summary} (${qualifiers})"
    fi

    IMAGE_SUMMARY="${summary}"
}

# record_state() - Records device provisioning state in SQLite database
#
# Parameters:
#   $1 (serial)   - Device serial number (e.g. "DC:A6:32:12:34:56")
#   $2 (state)    - Current state of device (e.g. "PROVISIONER-STARTED", "PROVISIONER-FINISHED")
#   $3 (endpoint) - Name of provisioning endpoint (e.g. "triage", "sb-provisioner", "fde-provisioner")
#   $4 (image)    - Optional one-line image summary.  Defaults to the global
#                   ${IMAGE_SUMMARY} (set by compute_image_summary), so callers
#                   that have run compute_image_summary need not pass it.
#   $5 (ip)       - Optional IP address.
#
# The function creates/updates an SQLite database at /var/log/rpi-sb-provisioner/state.db
# tracking the provisioning state of devices over time. Each state change creates a new
# record with a timestamp.

record_state() {
    touch /srv/rpi-sb-provisioner/state.db
    sqlite3 "/srv/rpi-sb-provisioner/state.db"      \
    -cmd "PRAGMA journal_mode=WAL;"                     \
    -cmd "PRAGMA busy_timeout=5000;"                    \
    "CREATE TABLE IF NOT EXISTS devices(                \
        id              integer primary key,            \
        serial          TEXT                not null,   \
        endpoint        TEXT                not null,   \
        state           TEXT                not null,   \
        image           TEXT                not null,   \
        ip_address      TEXT                not null,   \
        board_type      TEXT                not null default '', \
        ts              timestamp           default current_timestamp \
        );" > /dev/null 2>&1

    serial=$1
    state=$2
    endpoint=$3
    image=${4:-${IMAGE_SUMMARY:-}}
    ip_address=${5:-""}
    # Sticky board_type: callers don't have to thread it through every call --
    # any record_state after the bootstrap metadata extraction picks up the
    # global ${BOARD_TYPE} so the latest-row queries always see it.
    board_type=${6:-${BOARD_TYPE:-}}
    # Escape single quotes for safe inline SQL (image may contain spaces,
    # parentheses, etc. when it carries an IDP one-line summary).
    image=$(printf '%s' "${image}" | sed "s/'/''/g")
    board_type=$(printf '%s' "${board_type}" | sed "s/'/''/g")
    sqlite3 "/srv/rpi-sb-provisioner/state.db" "PRAGMA busy_timeout=5000; INSERT INTO devices(serial, endpoint, state, image, ip_address, board_type) VALUES ('$serial', '$endpoint', '$state', '$image', '$ip_address', '$board_type');" > /dev/null 2>&1

    # Nudge the UI service to rescan now that a row has been written. This
    # replaces the inotify watch the service used to keep on state.db, which
    # self-triggered against its own SQLite reads and pinned a core at 100%.
    # Fire-and-forget: the service may be down, restarting, or simply not
    # installed (API-only deployments), so any failure here must not break
    # provisioning. The body is intentionally empty -- the service re-reads
    # state.db on the resulting scan. Override the URL via the env var when
    # provisioner-service runs on a non-default port or address.
    #
    # The internal endpoint is gated by a per-boot token written by the
    # service to /run/rpi-sb-provisioner/internal.token with mode 0600. If
    # the file does not yet exist (service still starting), skip the call
    # entirely -- the worker's 10s backstop picks the row up either way.
    notify_url=${PROVISIONER_NOTIFY_URL:-http://127.0.0.1:3142/internal/state-changed}
    if [ -r /run/rpi-sb-provisioner/internal.token ]; then
        internal_token=$(cat /run/rpi-sb-provisioner/internal.token 2>/dev/null || true)
        if [ -n "${internal_token}" ]; then
            curl --max-time 1 --connect-timeout 1 -fsS -X POST -o /dev/null \
                -H "X-Internal-Token: ${internal_token}" \
                "${notify_url}" >/dev/null 2>&1 || true
        fi
    fi
}

# get_usb_path_for_serial() - Finds the USB path for a device with a specific serial number
#
# Parameters:
#   $1 (serial) - Device serial number to search for (e.g., "DC:A6:32:12:34:56")
#
# Returns:
#   Outputs the USB path in the format "X-Y[.Z]" where:
#   - X is the USB bus number
#   - Y is the port number on that bus
#   - Z is the optional port number for devices behind a hub (can be multiple levels)
#   Example: "1-1.2" means bus 1, hub port 1, hub downstream port 2
#
# Return codes:
#   0 - Success, USB path was found and output to stdout
#   1 - Failure, no matching device was found
#
# The function first searches in /sys/bus/usb/devices/ and falls back to using
# udevadm if the first method fails.
get_usb_path_for_serial() {
    serial="$1"
    usb_path=""
    
    # Find all USB devices
    for device_path in /sys/bus/usb/devices/*; do
        # Skip if not a directory or doesn't match the pattern we're looking for
        base_name=$(basename "$device_path")
        case "$base_name" in
            [0-9]*-[0-9]*|\
            [0-9]*-[0-9]*.[0-9]*|\
            [0-9]*-[0-9]*.[0-9]*.[0-9]*)
                if [ ! -d "$device_path" ]; then
                    continue
                fi
                ;;
            *)
                continue
                ;;
        esac
        
        # Check if this device has the serial number we're looking for
        if [ -f "$device_path/serial" ] && [ "$(cat "$device_path/serial")" = "$serial" ]; then
            usb_path="$(basename "$device_path")"
            echo "$usb_path"
            return 0
        fi
    done

    # Fallback to using udevadm to find the device if available
    if command -v udevadm >/dev/null 2>&1; then
        for device in /dev/bus/usb/*/*; do
            serial=$(udevadm info --name="$device" --query=property --property=ID_SERIAL_SHORT --value 2>/dev/null)
            if [ "$serial" = "${TARGET_DEVICE_SERIAL}" ]; then
                # Separate assignment from export to avoid masking return values
                usb_path="$(udevadm info "$device" | grep -oP '^P: \K.*' | grep -oE '[0-9]+-[0-9]+(\.[0-9]+)*$')"
                echo "$usb_path"
                return 0
            fi
        done
    fi
    
    # If we get here, we didn't find a matching device
    return 1
}