#!/usr/bin/env bash

set -u
set -o pipefail

EXIT_INVALID_ARGUMENT=10
EXIT_PERMISSION_DENIED=11
EXIT_KERNEL_VERSION_MISMATCH=12
EXIT_ARCH_MISMATCH=13
EXIT_KPATCH_INSTALL_FAILED=14
EXIT_DISK_SPACE_INSUFFICIENT=15
EXIT_RPM_DB_LOCKED=16
EXIT_LIVEPATCH_CONFLICT=17
EXIT_DOWNLOAD_FAILED=18
EXIT_RPM_VALIDATION_FAILED=19
EXIT_RPM_INSTALL_FAILED=20
EXIT_POST_INSTALL_VERIFY_FAILED=21
EXIT_CAPABILITY_NOT_SUPPORTED=22
EXIT_HOTFIX_NOT_FOUND=23
EXIT_LIVEPATCH_CHECK_FAILED=24
EXIT_STATUS_CHECK_FAILED=25
EXIT_RPM_UNINSTALL_FAILED=26
EXIT_FORCE_UNLOAD_FAILED=27

SYS_ROOT=${HOTFIXCTL_SYS_ROOT:-}
MIN_FREE_KB=${HOTFIXCTL_MIN_FREE_KB:-204800}
WORK_DIR=""
TARGET_RPM=""
TARGET_PACKAGE=""
TARGET_NEVRA=""
TARGET_MODULE=""
PROBE_RPM_INSTALLED=false
PROBE_MODULE_LOADED=false
PROBE_EFFECTIVE=false
PROBE_STATUS=""
PROBE_CONFLICTS=""
PROBE_INCONSISTENT=false
PROBE_IDENTITY_MISMATCH=false

json_escape() {
    printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n\r' '  '
}

emit_error() {
    local exit_code=$1
    local code=$2
    local message=$3
    printf '{"success":false,"code":"%s","message":"%s","data":{}}\n' \
        "$(json_escape "$code")" "$(json_escape "$message")"
    exit "$exit_code"
}

emit_install_success() {
    local cve=$1
    local package_name=$2
    local module_name=$3
    local idempotent=$4
    printf '{"success":true,"code":"Success","message":"","data":{"cveId":"%s","package":"%s","module":"%s","status":"Active","idempotent":%s}}\n' \
        "$(json_escape "$cve")" \
        "$(json_escape "$package_name")" \
        "$(json_escape "$module_name")" \
        "$idempotent"
}

emit_status_success() {
    local cve=$1
    printf '{"success":true,"code":"Success","message":"","data":{"cveId":"%s","package":"%s","module":"%s","status":"%s","rpmInstalled":%s,"moduleLoaded":%s,"effective":%s,"conflicts":"%s"}}\n' \
        "$(json_escape "$cve")" \
        "$(json_escape "$TARGET_NEVRA")" \
        "$(json_escape "$TARGET_MODULE")" \
        "$PROBE_STATUS" \
        "$PROBE_RPM_INSTALLED" \
        "$PROBE_MODULE_LOADED" \
        "$PROBE_EFFECTIVE" \
        "$(json_escape "$PROBE_CONFLICTS")"
}

emit_residual_module_error() {
    local cve=$1
    printf '{"success":false,"code":"RESIDUAL_MODULE","message":"Target module is loaded but the exact RPM is not installed","data":{"cveId":"%s","package":"%s","module":"%s","rpmInstalled":false,"moduleLoaded":true,"effective":%s}}\n' \
        "$(json_escape "$cve")" \
        "$(json_escape "$TARGET_NEVRA")" \
        "$(json_escape "$TARGET_MODULE")" \
        "$PROBE_EFFECTIVE"
    exit "$EXIT_STATUS_CHECK_FAILED"
}

emit_uninstall_success() {
    local cve=$1
    local idempotent=$2
    printf '{"success":true,"code":"Success","message":"","data":{"cveId":"%s","package":"%s","module":"%s","status":"NOT_INSTALLED","rpmInstalled":false,"moduleLoaded":false,"effective":false,"idempotent":%s}}\n' \
        "$(json_escape "$cve")" \
        "$(json_escape "$TARGET_NEVRA")" \
        "$(json_escape "$TARGET_MODULE")" \
        "$idempotent"
}

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

retry_command() {
    local attempts=$1
    shift
    local try_no=1
    while ! "$@"; do
        if (( try_no >= attempts )); then
            return 1
        fi
        sleep "$try_no"
        try_no=$((try_no + 1))
    done
}

validate_arguments() {
    local cve=$1
    local kernel=$2
    local arch=$3
    local url=$4
    local url_pattern='^https://[A-Za-z0-9./_%?=&:+@~-]+$'
    [[ $cve =~ ^CVE-[0-9]{4}-[0-9]{4,}$ ]] || \
        emit_error "$EXIT_INVALID_ARGUMENT" INVALID_ARGUMENT "Invalid CVE ID"
    [[ $kernel =~ ^[A-Za-z0-9._+:-]+$ ]] || \
        emit_error "$EXIT_INVALID_ARGUMENT" INVALID_ARGUMENT "Invalid kernel version"
    [[ $arch == "x86_64" || $arch == "aarch64" ]] || \
        emit_error "$EXIT_INVALID_ARGUMENT" INVALID_ARGUMENT "Unsupported architecture"
    [[ $url =~ $url_pattern ]] || \
        emit_error "$EXIT_INVALID_ARGUMENT" INVALID_ARGUMENT "Invalid HTTPS package URL"
}

ensure_root() {
    if [[ ${HOTFIXCTL_ALLOW_NON_ROOT:-0} != "1" && $(id -u) -ne 0 ]]; then
        emit_error "$EXIT_PERMISSION_DENIED" PERMISSION_DENIED \
            "hotfixctl must run as root"
    fi
}

ensure_target_matches() {
    local expected_kernel=$1
    local expected_arch=$2
    local actual_kernel=${HOTFIXCTL_KERNEL_VERSION:-$(uname -r)}
    local actual_arch=${HOTFIXCTL_ARCH:-$(uname -m)}
    [[ $actual_kernel == "$expected_kernel" ]] || \
        emit_error "$EXIT_KERNEL_VERSION_MISMATCH" KERNEL_VERSION_MISMATCH \
            "Expected kernel $expected_kernel, got $actual_kernel"
    [[ $actual_arch == "$expected_arch" ]] || \
        emit_error "$EXIT_ARCH_MISMATCH" ARCH_MISMATCH \
            "Expected architecture $expected_arch, got $actual_arch"
}

ensure_kpatch() {
    if command -v kpatch >/dev/null 2>&1; then
        return
    fi
    local manager=""
    if command -v dnf >/dev/null 2>&1; then
        manager=dnf
    elif command -v yum >/dev/null 2>&1; then
        manager=yum
    else
        emit_error "$EXIT_KPATCH_INSTALL_FAILED" KPATCH_INSTALL_FAILED \
            "Neither kpatch nor a supported package manager is available"
    fi
    retry_command 3 "$manager" install -y kpatch >/dev/null 2>&1 || \
        emit_error "$EXIT_KPATCH_INSTALL_FAILED" KPATCH_INSTALL_FAILED \
            "Unable to install kpatch"
    command -v kpatch >/dev/null 2>&1 || \
        emit_error "$EXIT_KPATCH_INSTALL_FAILED" KPATCH_INSTALL_FAILED \
            "kpatch is still unavailable after installation"
}

ensure_disk_space() {
    local target=$1
    local free_kb
    free_kb=$(df -Pk "$target" 2>/dev/null | awk 'NR==2 {print $4}')
    [[ $free_kb =~ ^[0-9]+$ ]] || \
        emit_error "$EXIT_DISK_SPACE_INSUFFICIENT" DISK_SPACE_INSUFFICIENT \
            "Unable to determine free disk space"
    (( free_kb >= MIN_FREE_KB )) || \
        emit_error "$EXIT_DISK_SPACE_INSUFFICIENT" DISK_SPACE_INSUFFICIENT \
            "At least ${MIN_FREE_KB}KB free space is required"
}

ensure_rpm_db() {
    command -v rpm >/dev/null 2>&1 || \
        emit_error "$EXIT_CAPABILITY_NOT_SUPPORTED" CAPABILITY_NOT_SUPPORTED \
            "rpm is unavailable"
    retry_command 3 rpm -qa >/dev/null 2>&1 || \
        emit_error "$EXIT_RPM_DB_LOCKED" RPM_DB_LOCKED \
            "RPM database is unavailable or locked"
}

ensure_livepatch_capability() {
    local kernel=$1
    if [[ -d "$SYS_ROOT/sys/kernel/livepatch" || -d "$SYS_ROOT/sys/kernel/kpatch" ]]; then
        return
    fi
    if [[ -r "$SYS_ROOT/boot/config-$kernel" ]] && \
        grep -q '^CONFIG_LIVEPATCH=y' "$SYS_ROOT/boot/config-$kernel"; then
        return
    fi
    emit_error "$EXIT_CAPABILITY_NOT_SUPPORTED" CAPABILITY_NOT_SUPPORTED \
        "Kernel livepatch capability is unavailable"
}

download_package() {
    local url=$1
    local destination=$2
    command -v curl >/dev/null 2>&1 || \
        emit_error "$EXIT_CAPABILITY_NOT_SUPPORTED" CAPABILITY_NOT_SUPPORTED \
            "curl is unavailable"
    curl -fsSL --retry 2 --connect-timeout 30 --max-time 240 \
        -o "$destination" "$url" >/dev/null 2>&1 || \
        emit_error "$EXIT_DOWNLOAD_FAILED" DOWNLOAD_FAILED \
            "Unable to download hotfix RPM"
    [[ -s $destination ]] || \
        emit_error "$EXIT_DOWNLOAD_FAILED" DOWNLOAD_FAILED \
            "Downloaded hotfix RPM is empty"
}

validate_rpm() {
    local rpm_file=$1
    rpm -K "$rpm_file" >/dev/null 2>&1 || \
        emit_error "$EXIT_RPM_VALIDATION_FAILED" RPM_VALIDATION_FAILED \
            "RPM digest or signature validation failed"
    local package_arch
    package_arch=$(rpm -qp --queryformat '%{ARCH}' "$rpm_file" 2>/dev/null) || \
        emit_error "$EXIT_RPM_VALIDATION_FAILED" RPM_VALIDATION_FAILED \
            "Unable to inspect RPM metadata"
    [[ $package_arch == "$ARCH" || $package_arch == "noarch" ]] || \
        emit_error "$EXIT_RPM_VALIDATION_FAILED" RPM_VALIDATION_FAILED \
            "RPM architecture $package_arch does not match $ARCH"
}

extract_modules() {
    local rpm_file=$1
    local extract_dir=$2
    command -v rpm2cpio >/dev/null 2>&1 && command -v cpio >/dev/null 2>&1 || \
        emit_error "$EXIT_CAPABILITY_NOT_SUPPORTED" CAPABILITY_NOT_SUPPORTED \
            "rpm2cpio and cpio are required for conflict checks"
    mkdir -p "$extract_dir"
    (cd "$extract_dir" && rpm2cpio "$rpm_file" | cpio -idm --quiet) || \
        emit_error "$EXIT_RPM_VALIDATION_FAILED" RPM_VALIDATION_FAILED \
            "Unable to extract hotfix RPM"
}

# Get patched functions for a loaded hotfix module from sysfs.
# References khotfix-view's get_funcs() design.
# Output format: OBJECT<TAB>FUNCTION<TAB>SYMPOS
get_patched_funcs() {
    local mod="$1"

    # Check kpatch sysfs (both /sys/kernel/kpatch/$mod and /sys/kernel/kpatch/patches/$mod
    # for compatibility with different kernel versions)
    local sysfs
    for sysfs in \
        "$SYS_ROOT/sys/kernel/kpatch/$mod" \
        "$SYS_ROOT/sys/kernel/kpatch/patches/$mod"; do
        [[ -d "$sysfs" ]] || continue
        local objdir objname funcentry entry funcname sympos
        for objdir in "$sysfs"/*/; do
            [[ -d "$objdir" ]] || continue
            objname="${objdir%/}"; objname="${objname##*/}"
            [[ "$objname" =~ ^(enabled|checksum)$ ]] && continue
            for funcentry in "$objdir"/*; do
                [[ -e "$funcentry" ]] || continue
                entry="${funcentry##*/}"
                funcname="${entry%%,*}"
                sympos="${entry##*,}"
                [[ "$sympos" == "$funcname" ]] && sympos="0"
                printf '%s\t%s\t%s\n' "$objname" "$funcname" "$sympos"
            done
        done
        return
    done

    # Check livepatch sysfs
    if [[ -d "$SYS_ROOT/sys/kernel/livepatch/$mod" ]]; then
        local objdir objname funcentry entry funcname sympos
        for objdir in "$SYS_ROOT/sys/kernel/livepatch/$mod"/*/; do
            [[ -d "$objdir" ]] || continue
            objname="${objdir%/}"; objname="${objname##*/}"
            [[ "$objname" =~ ^(enabled|transition|signal|force|replace|stack_order)$ ]] && continue
            for funcentry in "$objdir"/*; do
                [[ -e "$funcentry" ]] || continue
                entry="${funcentry##*/}"
                [[ "$entry" == "patched" ]] && continue
                funcname="${entry%%,*}"
                sympos="${entry##*,}"
                [[ "$sympos" == "$funcname" ]] && sympos="0"
                printf '%s\t%s\t%s\n' "$objname" "$funcname" "$sympos"
            done
        done
        return
    fi
}

# Collect all loaded hotfix module names from sysfs.
# References khotfix-view's collect_loaded() design (without alikernel/manual patches).
# Output: one module name per line (caller should deduplicate with sort -u)
collect_loaded_patches() {
    local mod

    # From livepatch sysfs
    if [[ -d "$SYS_ROOT/sys/kernel/livepatch" ]]; then
        for moddir in "$SYS_ROOT"/sys/kernel/livepatch/*/; do
            [[ -d "$moddir" ]] || continue
            mod="${moddir%/}"; mod="${mod##*/}"
            printf '%s\n' "$mod"
        done
    fi

    # From kpatch sysfs (both /sys/kernel/kpatch and /sys/kernel/kpatch/patches
    # for compatibility with different kernel versions)
    local sysfs
    for sysfs in \
        "$SYS_ROOT/sys/kernel/kpatch" \
        "$SYS_ROOT/sys/kernel/kpatch/patches"; do
        [[ -d "$sysfs" ]] || continue
        for moddir in "$sysfs"/*/; do
            [[ -d "$moddir" ]] || continue
            mod="${moddir%/}"; mod="${mod##*/}"
            [[ "$mod" == "patches" ]] && continue
            printf '%s\n' "$mod"
        done
    done
}

module_name_from_rpm() {
    local rpm_file=$1
    local module_path
    module_path=$(rpm -qlp "$rpm_file" 2>/dev/null | awk '/\.ko(\.xz)?$/ {print; exit}')
    [[ -n $module_path ]] || return 1

    # Extract the ko file from RPM and use modinfo to get the real module name.
    # The ko filename (e.g. kpatch-xxx.ko) may differ from the kernel module name
    # (e.g. kpatch_xxx) because hyphens in filenames become underscores in module names.
    local tmp_dir
    tmp_dir=$(mktemp -d "${TMPDIR:-/var/tmp}/hotfixctl-modname.XXXXXX") || return 1
    local name=""
    (cd "$tmp_dir" && rpm2cpio "$rpm_file" 2>/dev/null | cpio -idm --quiet 2>/dev/null) || true
    local ko_file
    ko_file=$(find "$tmp_dir" \( -name '*.ko' -o -name '*.ko.xz' \) -print -quit 2>/dev/null)
    if [[ -n $ko_file && -f $ko_file ]]; then
        if command -v modinfo >/dev/null 2>&1; then
            name=$(modinfo -F name "$ko_file" 2>/dev/null)
        fi
        if [[ -z $name ]] && command -v readelf >/dev/null 2>&1; then
            name=$(readelf -p .gnu.linkonce.this_module "$ko_file" 2>/dev/null | awk '/\[/{print $3}')
        fi
        # Last resort: derive from filename with hyphen-to-underscore conversion
        if [[ -z $name ]]; then
            name=$(basename "$ko_file")
            name=${name%.ko.xz}
            name=${name%.ko}
            name=${name//-/_}
        fi
    fi
    rm -rf -- "$tmp_dir"
    [[ -n $name ]] || return 1
    printf '%s\n' "$name"
}

prepare_target_probe() {
    local kernel=$1
    local url=$2

    WORK_DIR=$(mktemp -d "${TMPDIR:-/var/tmp}/hotfixctl.XXXXXX") || \
        emit_error "$EXIT_DISK_SPACE_INSUFFICIENT" DISK_SPACE_INSUFFICIENT \
            "Unable to create temporary directory"
    ensure_disk_space "$WORK_DIR"
    ensure_rpm_db
    ensure_livepatch_capability "$kernel"

    TARGET_RPM="$WORK_DIR/hotfix.rpm"
    download_package "$url" "$TARGET_RPM"
    validate_rpm "$TARGET_RPM"
    TARGET_PACKAGE=$(rpm -qp --queryformat '%{NAME}' "$TARGET_RPM" 2>/dev/null) || \
        emit_error "$EXIT_HOTFIX_NOT_FOUND" HOTFIX_NOT_FOUND \
            "Unable to determine the target RPM name"
    TARGET_NEVRA=$(rpm -qp --queryformat '%{NEVRA}' "$TARGET_RPM" 2>/dev/null) || \
        emit_error "$EXIT_HOTFIX_NOT_FOUND" HOTFIX_NOT_FOUND \
            "Unable to determine the target RPM identity"
    TARGET_MODULE=$(module_name_from_rpm "$TARGET_RPM") || \
        emit_error "$EXIT_HOTFIX_NOT_FOUND" HOTFIX_NOT_FOUND \
            "Hotfix RPM does not identify a kernel module"
}

target_module_loaded() {
    local mod_underscore="${TARGET_MODULE//-/_}"
    [[ -d "$SYS_ROOT/sys/module/$mod_underscore" ]] || \
        [[ -d "$SYS_ROOT/sys/kernel/livepatch/$TARGET_MODULE" ]] || \
        [[ -d "$SYS_ROOT/sys/kernel/kpatch/$TARGET_MODULE" ]] || \
        [[ -d "$SYS_ROOT/sys/kernel/kpatch/patches/$TARGET_MODULE" ]]
}

target_module_effective() {
    local enabled=""
    local sysfs

    # Check kpatch sysfs (both /sys/kernel/kpatch/$mod and /sys/kernel/kpatch/patches/$mod)
    for sysfs in \
        "$SYS_ROOT/sys/kernel/kpatch/$TARGET_MODULE" \
        "$SYS_ROOT/sys/kernel/kpatch/patches/$TARGET_MODULE"; do
        if [[ -f "$sysfs/enabled" ]]; then
            enabled=$(tr -d '[:space:]' <"$sysfs/enabled")
            [[ "$enabled" == "1" ]] && return 0 || return 1
        fi
    done

    # Check livepatch sysfs
    if [[ -f "$SYS_ROOT/sys/kernel/livepatch/$TARGET_MODULE/enabled" ]]; then
        enabled=$(tr -d '[:space:]' <"$SYS_ROOT/sys/kernel/livepatch/$TARGET_MODULE/enabled")
        [[ "$enabled" == "1" ]] && return 0 || return 1
    fi

    return 1
}

detect_installed_conflicts() {
    local records="$WORK_DIR/installed-functions"
    local conflicts="$WORK_DIR/installed-conflicts"
    : >"$records"
    : >"$conflicts"

    # Collect all functions from all loaded patches via sysfs.
    # References khotfix-view's get_funcs() + cmd_conflicts() design.
    # Records format: OBJECT<TAB>FUNCTION<TAB>SYMPOS<TAB>MODULE
    local mod
    while IFS= read -r mod; do
        [[ -z "$mod" ]] && continue
        local obj func sympos
        while IFS=$'\t' read -r obj func sympos; do
            [[ -z "$func" ]] && continue
            printf '%s\t%s\t%s\t%s\n' "$obj" "$func" "$sympos" "$mod" >>"$records"
        done < <(get_patched_funcs "$mod")
    done < <(collect_loaded_patches | sort -u)

    [[ -s "$records" ]] || return 0

    # Find conflicts: same OBJECT+FUNCTION+SYMPOS patched by multiple modules
    sort -u "$records" -o "$records"
    awk -F'\t' '
        {
            key = $1 SUBSEP $2 SUBSEP $3
            if (key in mods) {
                if (index(mods[key], $4) == 0)
                    mods[key] = mods[key] "," $4
            } else {
                mods[key] = $4
            }
            keys[key] = $1 "\t" $2 "\t" $3
        }
        END {
            for (k in mods) {
                n = split(mods[k], arr, ",")
                if (n > 1) {
                    print keys[k] "\t" mods[k]
                }
            }
        }
    ' "$records" | sort >"$conflicts"

    if [[ -s "$conflicts" ]]; then
        PROBE_CONFLICTS=$(awk -F'\t' '{print $2}' "$conflicts" | sort -u | paste -sd, -)
    else
        PROBE_CONFLICTS=""
    fi
}

probe_target_state() {
    PROBE_RPM_INSTALLED=false
    PROBE_MODULE_LOADED=false
    PROBE_EFFECTIVE=false
    PROBE_INCONSISTENT=false
    PROBE_IDENTITY_MISMATCH=false

    if rpm -q "$TARGET_NEVRA" >/dev/null 2>&1; then
        PROBE_RPM_INSTALLED=true
    elif rpm -q "$TARGET_PACKAGE" >/dev/null 2>&1; then
        PROBE_IDENTITY_MISMATCH=true
    fi
    if target_module_loaded; then
        PROBE_MODULE_LOADED=true
        if target_module_effective; then
            PROBE_EFFECTIVE=true
        fi
    fi
    detect_installed_conflicts

    if [[ -n $PROBE_CONFLICTS ]]; then
        PROBE_STATUS=CONFLICT
    elif [[ $PROBE_RPM_INSTALLED == false && $PROBE_MODULE_LOADED == false ]]; then
        PROBE_STATUS=NOT_INSTALLED
    elif [[ $PROBE_RPM_INSTALLED == true && \
        $PROBE_MODULE_LOADED == true && $PROBE_EFFECTIVE == true ]]; then
        PROBE_STATUS=LOADED_EFFECTIVE
    elif [[ $PROBE_RPM_INSTALLED == true ]]; then
        PROBE_STATUS=INSTALLED_NOT_LOADED
    else
        PROBE_INCONSISTENT=true
        PROBE_STATUS=""
    fi
}

status_hotfix() {
    local cve=$1
    local expected_kernel=$2
    local expected_arch=$3
    local url=$4

    ensure_root
    ensure_target_matches "$expected_kernel" "$expected_arch"
    prepare_target_probe "$expected_kernel" "$url"
    probe_target_state
    [[ $PROBE_IDENTITY_MISMATCH == false ]] || \
        emit_error "$EXIT_STATUS_CHECK_FAILED" HOTFIX_IDENTITY_MISMATCH \
            "A different RPM identity is installed for the target package name"
    [[ $PROBE_INCONSISTENT == false ]] || emit_residual_module_error "$cve"
    emit_status_success "$cve"
}

uninstall_hotfix() {
    local cve=$1
    local expected_kernel=$2
    local expected_arch=$3
    local url=$4

    ensure_root
    ensure_target_matches "$expected_kernel" "$expected_arch"
    prepare_target_probe "$expected_kernel" "$url"
    probe_target_state
    [[ $PROBE_IDENTITY_MISMATCH == false ]] || \
        emit_error "$EXIT_STATUS_CHECK_FAILED" HOTFIX_IDENTITY_MISMATCH \
            "Refusing to uninstall a different RPM identity"

    if [[ $PROBE_RPM_INSTALLED == false && $PROBE_MODULE_LOADED == false ]]; then
        emit_uninstall_success "$cve" true
        return
    fi

    if [[ $PROBE_RPM_INSTALLED == true ]]; then
        rpm -e "$TARGET_NEVRA" >/dev/null 2>&1 || \
            emit_error "$EXIT_RPM_UNINSTALL_FAILED" RPM_UNINSTALL_FAILED \
                "Unable to uninstall the exact hotfix RPM"
    fi

    if target_module_loaded; then
        ensure_kpatch
        kpatch unload "$TARGET_MODULE" >/dev/null 2>&1 || \
            emit_error "$EXIT_FORCE_UNLOAD_FAILED" FORCE_UNLOAD_FAILED \
                "Unable to force unload the residual hotfix module"
    fi

    if rpm -q "$TARGET_NEVRA" >/dev/null 2>&1 || target_module_loaded; then
        emit_error "$EXIT_STATUS_CHECK_FAILED" STATUS_CHECK_FAILED \
            "Hotfix RPM or module remains after uninstall"
    fi
    emit_uninstall_success "$cve" false
}

verify_active() {
    local package_name=$1
    local module_name=$2
    rpm -q "$package_name" >/dev/null 2>&1 || return 1

    local mod_underscore="${module_name//-/_}"
    [[ -d "$SYS_ROOT/sys/module/$mod_underscore" ]] || \
        [[ -d "$SYS_ROOT/sys/kernel/livepatch/$module_name" ]] || \
        [[ -d "$SYS_ROOT/sys/kernel/kpatch/$module_name" ]] || \
        [[ -d "$SYS_ROOT/sys/kernel/kpatch/patches/$module_name" ]] || return 1

    local enabled=""
    local sysfs

    # Check kpatch sysfs (both /sys/kernel/kpatch/$mod and /sys/kernel/kpatch/patches/$mod)
    for sysfs in \
        "$SYS_ROOT/sys/kernel/kpatch/$module_name" \
        "$SYS_ROOT/sys/kernel/kpatch/patches/$module_name"; do
        if [[ -f "$sysfs/enabled" ]]; then
            enabled=$(tr -d '[:space:]' <"$sysfs/enabled")
            [[ "$enabled" == "1" ]] && return 0 || return 1
        fi
    done

    # Check livepatch sysfs
    if [[ -f "$SYS_ROOT/sys/kernel/livepatch/$module_name/enabled" ]]; then
        enabled=$(tr -d '[:space:]' <"$SYS_ROOT/sys/kernel/livepatch/$module_name/enabled")
        [[ "$enabled" == "1" ]] && return 0 || return 1
    fi

    return 1
}

install_hotfix() {
    local cve=$1
    local expected_kernel=$2
    local expected_arch=$3
    local url=$4

    ensure_root
    ensure_target_matches "$expected_kernel" "$expected_arch"
    ensure_kpatch

    WORK_DIR=$(mktemp -d "${TMPDIR:-/var/tmp}/hotfixctl.XXXXXX") || \
        emit_error "$EXIT_DISK_SPACE_INSUFFICIENT" DISK_SPACE_INSUFFICIENT \
            "Unable to create temporary directory"
    ensure_disk_space "$WORK_DIR"
    ensure_rpm_db
    ensure_livepatch_capability "$expected_kernel"

    local rpm_file="$WORK_DIR/hotfix.rpm"
    download_package "$url" "$rpm_file"
    validate_rpm "$rpm_file"

    local package_name
    package_name=$(rpm -qp --queryformat '%{NAME}' "$rpm_file" 2>/dev/null) || \
        emit_error "$EXIT_RPM_VALIDATION_FAILED" RPM_VALIDATION_FAILED \
            "Unable to read RPM package name"
    local rpm_nevra
    rpm_nevra=$(rpm -qp --queryformat '%{NEVRA}' "$rpm_file" 2>/dev/null) || \
        emit_error "$EXIT_RPM_VALIDATION_FAILED" RPM_VALIDATION_FAILED \
            "Unable to read RPM identity"
    local module_name
    module_name=$(module_name_from_rpm "$rpm_file") || \
        emit_error "$EXIT_RPM_VALIDATION_FAILED" RPM_VALIDATION_FAILED \
            "Hotfix RPM does not contain a kernel module"

    if rpm -q "$rpm_nevra" >/dev/null 2>&1; then
        verify_active "$package_name" "$module_name" || \
            emit_error "$EXIT_POST_INSTALL_VERIFY_FAILED" POST_INSTALL_VERIFY_FAILED \
                "RPM is installed but hotfix is not active"
        emit_install_success "$cve" "$package_name" "$module_name" true
        return
    fi
    rpm -q "$package_name" >/dev/null 2>&1 && \
        emit_error "$EXIT_STATUS_CHECK_FAILED" HOTFIX_IDENTITY_MISMATCH \
            "A different RPM identity is already installed"

    retry_command 2 rpm -ivh "$rpm_file" >/dev/null 2>&1 || \
        emit_error "$EXIT_RPM_INSTALL_FAILED" RPM_INSTALL_FAILED \
            "Unable to install hotfix RPM"
    verify_active "$package_name" "$module_name" || \
        emit_error "$EXIT_POST_INSTALL_VERIFY_FAILED" POST_INSTALL_VERIFY_FAILED \
            "RPM installed but module or livepatch state is not active"

    # Post-install conflict check: verify no function conflicts among all loaded patches
    detect_installed_conflicts
    if [[ -n $PROBE_CONFLICTS ]]; then
        emit_error "$EXIT_LIVEPATCH_CONFLICT" LIVEPATCH_CONFLICT \
            "Hotfix installed but function conflicts detected: $PROBE_CONFLICTS"
    fi

    emit_install_success "$cve" "$package_name" "$module_name" false
}

usage() {
    printf 'Usage: hotfixctl {install|uninstall|status} --cve CVE-ID --kernel VERSION --arch ARCH --url HTTPS_URL\n' >&2
}

main() {
    [[ $# -gt 0 ]] || {
        usage
        emit_error "$EXIT_INVALID_ARGUMENT" INVALID_ARGUMENT "Missing operation"
    }
    local operation=$1
    shift
    case $operation in
        install|uninstall|status) ;;
        *) emit_error "$EXIT_INVALID_ARGUMENT" INVALID_ARGUMENT \
            "Unsupported operation: $operation" ;;
    esac

    local cve="" kernel="" arch="" url=""
    while [[ $# -gt 0 ]]; do
        case $1 in
            --cve) [[ $# -ge 2 ]] || break; cve=$2; shift 2 ;;
            --kernel) [[ $# -ge 2 ]] || break; kernel=$2; shift 2 ;;
            --arch) [[ $# -ge 2 ]] || break; arch=$2; shift 2 ;;
            --url) [[ $# -ge 2 ]] || break; url=$2; shift 2 ;;
            *) emit_error "$EXIT_INVALID_ARGUMENT" INVALID_ARGUMENT "Unknown argument: $1" ;;
        esac
    done
    [[ -n $cve && -n $kernel && -n $arch && -n $url ]] || \
        emit_error "$EXIT_INVALID_ARGUMENT" INVALID_ARGUMENT "Missing required argument"
    validate_arguments "$cve" "$kernel" "$arch" "$url"

    ARCH=$arch
    case $operation in
        install) install_hotfix "$cve" "$kernel" "$arch" "$url" ;;
        uninstall) uninstall_hotfix "$cve" "$kernel" "$arch" "$url" ;;
        status) status_hotfix "$cve" "$kernel" "$arch" "$url" ;;
    esac
}

main "$@"
