#!/usr/bin/env bash
set -euo pipefail

# tune.sh — Main tuning engine for tunectl
# Reads tune-manifest.json, applies tuning based on tier selection.
#
# Usage:
#   tune.sh --tier <conservative|balanced|aggressive> [--dry-run|--apply]
#
# Exit codes: 0=success, 1=failure, 2=usage error

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MANIFEST="${SCRIPT_DIR}/../tune-manifest.json"
BACKUP_ROOT="/var/lib/tunectl/backups"

# -------------------------------------------------------
# Usage / help
# -------------------------------------------------------
usage() {
  cat >&2 <<EOF
Usage: tune.sh --tier <tier> [--dry-run|--apply]

Options:
  --tier <tier>   Required. One of: conservative, balanced, aggressive
  --dry-run       Show plan without making changes (default)
  --apply         Apply tuning changes (requires root)
  --help          Show this help message

Tiers:
  conservative    risk=none entries only (51 entries)
  balanced        risk=none + risk=low entries (80 entries)
  aggressive      all entries including risk=med (86 entries)

Exit codes:
  0  Success
  1  Operational failure
  2  Usage error
EOF
  exit 2
}

# -------------------------------------------------------
# Globals
# -------------------------------------------------------
TIER=""
MODE="dry-run"

# -------------------------------------------------------
# Parse arguments
# -------------------------------------------------------
parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --tier)
        [[ $# -lt 2 ]] && { echo "Error: --tier requires a value" >&2; usage; }
        TIER="$2"
        shift 2
        ;;
      --dry-run)
        MODE="dry-run"
        shift
        ;;
      --apply)
        MODE="apply"
        shift
        ;;
      --help|-h)
        usage
        ;;
      *)
        echo "Error: Unknown argument: $1" >&2
        usage
        ;;
    esac
  done

  # Require --tier
  if [[ -z "$TIER" ]]; then
    echo "Error: --tier is required" >&2
    usage
  fi

  # Validate tier
  case "$TIER" in
    conservative|balanced|aggressive) ;;
    *)
      echo "Error: Invalid tier '$TIER'. Valid tiers: conservative, balanced, aggressive" >&2
      exit 2
      ;;
  esac

  # Apply mode requires root
  if [[ "$MODE" == "apply" && $EUID -ne 0 ]]; then
    echo "Error: --apply requires root privileges. Run with sudo." >&2
    exit 1
  fi
}

# -------------------------------------------------------
# Validate manifest
# -------------------------------------------------------
validate_manifest() {
  if [[ ! -f "$MANIFEST" ]]; then
    echo "Error: Manifest not found at $MANIFEST" >&2
    exit 1
  fi

  if ! jq empty "$MANIFEST" 2>/dev/null; then
    echo "Error: Manifest is not valid JSON: $MANIFEST" >&2
    exit 1
  fi
}

# -------------------------------------------------------
# Detect system RAM in KB
# -------------------------------------------------------
detect_ram_kb() {
  awk '/^MemTotal:/ {print $2}' /proc/meminfo 2>/dev/null || echo "0"
}

# -------------------------------------------------------
# Get tier jq filter expression
# -------------------------------------------------------
get_tier_filter() {
  case "$TIER" in
    conservative) echo 'select(.risk == "none")' ;;
    balanced)     echo 'select(.risk == "none" or .risk == "low")' ;;
    aggressive)   echo '.' ;;
  esac
}

# -------------------------------------------------------
# Round up to the nearest power of 2 (for memory sizes)
# -------------------------------------------------------
round_to_power_of_2() {
  local val="$1"
  awk "BEGIN {
    v = $val
    if (v <= 0) { print 1; exit }
    p = 1
    while (p < v) p *= 2
    # Pick the nearest power of 2 (up or down)
    lower = p / 2
    if ((v - lower) <= (p - v)) print lower
    else print p
  }"
}

# -------------------------------------------------------
# Scale a numeric value proportionally based on RAM
# Reference: 8GB
# Memory sizes are rounded to the nearest power of 2.
# -------------------------------------------------------
scale_value() {
  local entry_id="$1"
  local raw_value="$2"
  local ram_kb="$3"
  local ram_gb
  ram_gb=$(awk "BEGIN {printf \"%.2f\", $ram_kb / 1048576}")

  local ref_ram_gb=8

  case "$entry_id" in
    MEM-006)
      local scaled
      scaled=$(awk "BEGIN {v = 131072 * ($ram_gb / $ref_ram_gb); printf \"%.0f\", v}")
      scaled=$(round_to_power_of_2 "$scaled")
      [[ $scaled -lt 65536 ]] && scaled=65536
      echo "$scaled"
      ;;
    SWAP-007)
      local size_gb
      size_gb=$(awk "BEGIN {v = $ram_gb * 1.5; printf \"%.0f\", v}")
      [[ $size_gb -lt 1 ]] && size_gb=1
      echo "${size_gb}G"
      ;;
    FS-003)
      local size_gb
      size_gb=$(awk "BEGIN {v = $ram_gb * 0.5; printf \"%.0f\", v}")
      [[ $size_gb -lt 1 ]] && size_gb=1
      echo "tmpfs /tmp tmpfs defaults,noatime,size=${size_gb}G 0 0"
      ;;
    FS-006)
      local scaled
      scaled=$(awk "BEGIN {v = 524288 * ($ram_gb / $ref_ram_gb); printf \"%.0f\", v}")
      scaled=$(round_to_power_of_2 "$scaled")
      [[ $scaled -lt 65536 ]] && scaled=65536
      echo "$scaled"
      ;;
    CGRP-002)
      local size_gb
      size_gb=$(awk "BEGIN {v = $ram_gb * 0.6; printf \"%.0f\", v}")
      [[ $size_gb -lt 1 ]] && size_gb=1
      echo "${size_gb}G"
      ;;
    CGRP-008)
      local size_gb
      size_gb=$(awk "BEGIN {v = $ram_gb * 0.35; printf \"%.0f\", v}")
      [[ $size_gb -lt 1 ]] && size_gb=1
      echo "${size_gb}G"
      ;;
    *)
      echo "$raw_value"
      ;;
  esac
}

# -------------------------------------------------------
# Check if jemalloc library exists
# -------------------------------------------------------
check_jemalloc() {
  [[ -f "/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" ]] && return 0
  ldconfig -p 2>/dev/null | grep -q libjemalloc && return 0
  return 1
}

# -------------------------------------------------------
# Check if a systemd service unit file exists
# -------------------------------------------------------
service_exists() {
  local svc="$1"
  systemctl list-unit-files "$svc" 2>/dev/null | grep -q "$svc"
}

# -------------------------------------------------------
# Get current value for a sysctl parameter
# -------------------------------------------------------
get_sysctl_value() {
  sysctl -n "$1" 2>/dev/null || echo "not set"
}

# -------------------------------------------------------
# DRY-RUN: Display the tuning plan
# -------------------------------------------------------
do_dry_run() {
  local ram_kb
  ram_kb=$(detect_ram_kb)
  local ram_mb=$((ram_kb / 1024))
  local ram_gb
  ram_gb=$(awk "BEGIN {printf \"%.1f\", $ram_kb / 1048576}")

  local filter
  filter=$(get_tier_filter)

  echo "============================================"
  echo "  tunectl tune plan — Tier: $TIER (DRY RUN)"
  echo "============================================"
  echo ""
  echo "System RAM: ${ram_mb} MB (${ram_gb} GB)"
  echo ""

  # Extract all filtered entries as tab-separated lines in a SINGLE jq call
  local tsv_data
  tsv_data=$(jq -r "[.tuning_entries[] | $filter] | .[] | [.id, .category, .parameter, .config_file, .before_value, .after_value, .risk, (.requires_reboot | tostring), (.scaling_note // \"\")] | @tsv" "$MANIFEST")

  local count
  count=$(echo "$tsv_data" | wc -l)

  echo "Entries to apply: $count"
  echo ""

  # Category summary from the same data
  echo "--- Category Summary ---"
  echo "$tsv_data" | awk -F'\t' '{print $2}' | sort | uniq -c | sort -rn | while read -r cnt cat; do
    echo "  $cat: $cnt"
  done
  echo ""

  # Reboot required count
  local reboot_count
  reboot_count=$(echo "$tsv_data" | awk -F'\t' '$8 == "true"' | wc -l)
  if [[ $reboot_count -gt 0 ]]; then
    echo "⚠  $reboot_count entries require reboot"
    echo ""
  fi

  echo "--- Tuning Plan ---"
  printf "%-12s %-42s %-40s %-20s → %-20s %s\n" "ID" "Parameter" "Config File" "Current" "Target" "Notes"
  printf '%0.s-' {1..160}
  echo ""

  # Pre-fetch all sysctl values in one batch for speed
  declare -A sysctl_cache
  local sysctl_params
  sysctl_params=$(echo "$tsv_data" | awk -F'\t' '$4 == "/etc/sysctl.d/99-performance.conf" {print $3}')
  if [[ -n "$sysctl_params" ]]; then
    while IFS= read -r param; do
      sysctl_cache["$param"]=$(sysctl -n "$param" 2>/dev/null || echo "not set")
    done <<< "$sysctl_params"
  fi

  # Pre-check which services exist (batch)
  declare -A svc_exists_cache
  local svc_names
  svc_names=$(echo "$tsv_data" | awk -F'\t' '$4 == "systemctl disable/mask" {print $3}')
  if [[ -n "$svc_names" ]]; then
    # Get all unit files in one call (include both services and sockets)
    local all_units
    all_units=$( (systemctl list-unit-files --type=service --no-legend 2>/dev/null; systemctl list-unit-files --type=socket --no-legend 2>/dev/null) | awk '{print $1}' || echo "")
    while IFS= read -r svc; do
      [[ -z "$svc" ]] && continue
      if echo "$all_units" | grep -qF "$svc"; then
        svc_exists_cache["$svc"]="yes"
      else
        svc_exists_cache["$svc"]="no"
      fi
    done <<< "$svc_names"
  fi

  # Check jemalloc once
  local has_jemalloc=false
  check_jemalloc && has_jemalloc=true

  # Iterate entries
  while IFS=$'\t' read -r id category parameter config_file before_value after_value risk requires_reboot scaling_note; do
    # Compute target value (with RAM scaling)
    local target_value
    target_value=$(scale_value "$id" "$after_value" "$ram_kb")

    # Get current value
    local current_value=""
    case "$config_file" in
      /etc/sysctl.d/99-performance.conf)
        current_value="${sysctl_cache[$parameter]:-not set}"
        ;;
      "systemctl disable/mask")
        if [[ "${svc_exists_cache[$parameter]:-no}" == "yes" ]]; then
          current_value=$(systemctl is-enabled "$parameter" 2>/dev/null || echo "unknown")
        else
          current_value="not installed"
        fi
        ;;
      "systemctl")
        current_value="active"
        ;;
      /etc/environment)
        case "$id" in
          MEM-019) current_value=$(grep "^LD_PRELOAD=" /etc/environment 2>/dev/null | cut -d= -f2- || echo "not set") ;;
          BUILD-001) current_value=$(grep "^RUSTC_WRAPPER=" /etc/environment 2>/dev/null | cut -d= -f2- || echo "not set") ;;
          *) current_value="not set" ;;
        esac
        [[ -z "$current_value" ]] && current_value="not set"
        ;;
      /etc/default/grub)
        # Extract specific boot parameter from GRUB_CMDLINE_LINUX_DEFAULT
        current_value="not set"
        if [[ -f "$config_file" ]]; then
          local grub_cmdline
          grub_cmdline=$(grep '^GRUB_CMDLINE_LINUX_DEFAULT=' "$config_file" 2>/dev/null | sed 's/^GRUB_CMDLINE_LINUX_DEFAULT=//' | tr -d '"' || echo "")
          local grub_param_name=""
          case "$id" in
            SWAP-009) grub_param_name="zswap.enabled" ;;
            BOOT-001) grub_param_name="mitigations" ;;
            BOOT-002) grub_param_name="l1tf" ;;
            BOOT-003) grub_param_name="tsx_async_abort" ;;
            BOOT-004) grub_param_name="preempt" ;;
            BOOT-005) grub_param_name="transparent_hugepage" ;;
          esac
          if [[ -n "$grub_param_name" && -n "$grub_cmdline" ]]; then
            local grub_val
            grub_val=$(echo "$grub_cmdline" | grep -oE "${grub_param_name}=[^ ]+" | head -1 | cut -d= -f2-)
            if [[ -n "$grub_val" ]]; then
              current_value="$grub_val"
            fi
          fi
        fi
        ;;
      /etc/fstab)
        # Extract fstab entry details
        current_value="not configured"
        if [[ -f "$config_file" ]]; then
          case "$id" in
            SWAP-005)
              if grep -q '/dev/zram0' "$config_file" 2>/dev/null; then
                current_value=$(grep '/dev/zram0' "$config_file" | head -1 | sed 's/\s\+/ /g')
              fi
              ;;
            FS-001)
              local root_opts
              root_opts=$(awk '$2=="/" {print $4}' "$config_file" 2>/dev/null)
              if [[ -n "$root_opts" ]]; then
                if echo "$root_opts" | grep -q 'noatime'; then
                  current_value="noatime"
                elif echo "$root_opts" | grep -q 'relatime'; then
                  current_value="relatime"
                else
                  current_value="defaults"
                fi
              fi
              ;;
            FS-002)
              local root_opts
              root_opts=$(awk '$2=="/" {print $4}' "$config_file" 2>/dev/null)
              if [[ -n "$root_opts" ]]; then
                local commit_val
                commit_val=$(echo "$root_opts" | grep -oE 'commit=[0-9]+' | cut -d= -f2)
                if [[ -n "$commit_val" ]]; then
                  current_value="${commit_val} (seconds)"
                else
                  current_value="5 (default)"
                fi
              fi
              ;;
            FS-003)
              if grep -q 'tmpfs.*\/tmp' "$config_file" 2>/dev/null; then
                current_value=$(grep 'tmpfs.*\/tmp' "$config_file" | head -1 | sed 's/\s\+/ /g')
              fi
              ;;
          esac
        fi
        ;;
      /etc/tmpfiles.d/thp.conf|/etc/tmpfiles.d/ksm.conf)
        # Read live values from /sys/kernel/mm/
        current_value="not set"
        case "$id" in
          MEM-012) current_value=$(cat /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null | grep -oE '\[([a-z]+)\]' | tr -d '[]' || echo "not set") ;;
          MEM-013) current_value=$(cat /sys/kernel/mm/transparent_hugepage/defrag 2>/dev/null | grep -oE '\[([a-z+]+)\]' | tr -d '[]' || echo "not set") ;;
          MEM-014) current_value=$(cat /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs 2>/dev/null || echo "not set") ;;
          MEM-015) current_value=$(cat /sys/kernel/mm/ksm/run 2>/dev/null || echo "not set") ;;
          MEM-016) current_value=$(cat /sys/kernel/mm/ksm/pages_to_scan 2>/dev/null || echo "not set") ;;
          MEM-017) current_value=$(cat /sys/kernel/mm/ksm/sleep_millisecs 2>/dev/null || echo "not set") ;;
          MEM-018) current_value=$(cat /sys/kernel/mm/ksm/use_zero_pages 2>/dev/null || echo "not set") ;;
        esac
        ;;
      /etc/systemd/system/*.slice)
        # Read from systemctl show or parse unit file
        current_value="not present"
        local slice_name
        slice_name=$(basename "$config_file")
        local prop_name=""
        case "$parameter" in
          *CPUWeight*)    prop_name="CPUWeight" ;;
          *MemoryHigh*)   prop_name="MemoryHigh" ;;
          *IOWeight*)     prop_name="IOWeight" ;;
          *ManagedOOMSwap)                prop_name="ManagedOOMSwap" ;;
          *ManagedOOMMemoryPressureLimit) prop_name="ManagedOOMMemoryPressureLimit" ;;
          *ManagedOOMMemoryPressure)      prop_name="ManagedOOMMemoryPressure" ;;
        esac
        if [[ -n "$prop_name" ]]; then
          local prop_val
          prop_val=$(systemctl show "$slice_name" -p "$prop_name" 2>/dev/null | cut -d= -f2-)
          if [[ -n "$prop_val" && "$prop_val" != "infinity" && "$prop_val" != "[not set]" ]]; then
            # Convert bytes to human-readable for MemoryHigh
            if [[ "$prop_name" == "MemoryHigh" && "$prop_val" =~ ^[0-9]+$ ]]; then
              local gb_val
              gb_val=$(awk "BEGIN {printf \"%.0f\", $prop_val / 1073741824}")
              current_value="${gb_val}G"
            # Convert ManagedOOMMemoryPressureLimit from raw number to percentage
            elif [[ "$prop_name" == "ManagedOOMMemoryPressureLimit" && "$prop_val" =~ ^[0-9]+$ ]]; then
              # systemd reports as basis points (0-10000) or raw. Convert to %
              local pct
              pct=$(awk "BEGIN {v = $prop_val * 100 / 4294967295; printf \"%.0f\", v}")
              current_value="${pct}%"
            else
              current_value="$prop_val"
            fi
          fi
        fi
        ;;
      /etc/udev/rules.d/*)
        # Read live values from /sys/block/ for I/O and zram
        current_value="not set"
        case "$id" in
          FS-004)
            local sched
            sched=$(cat /sys/block/vda/queue/scheduler 2>/dev/null || cat /sys/block/sda/queue/scheduler 2>/dev/null || echo "")
            if [[ -n "$sched" ]]; then
              current_value=$(echo "$sched" | grep -oE '\[[a-z_-]+\]' | tr -d '[]' || echo "unknown")
            fi
            ;;
          FS-005)
            current_value=$(cat /sys/block/vda/queue/read_ahead_kb 2>/dev/null || cat /sys/block/sda/queue/read_ahead_kb 2>/dev/null || echo "not set")
            ;;
          SWAP-006)
            local algo
            algo=$(cat /sys/block/zram0/comp_algorithm 2>/dev/null || echo "")
            if [[ -n "$algo" ]]; then
              current_value=$(echo "$algo" | grep -oE '\[[a-z0-9_-]+\]' | tr -d '[]' || echo "not configured")
            fi
            ;;
          SWAP-007)
            local disksize_bytes
            disksize_bytes=$(cat /sys/block/zram0/disksize 2>/dev/null || echo "0")
            if [[ "$disksize_bytes" -gt 0 ]] 2>/dev/null; then
              local disksize_gb
              disksize_gb=$(awk "BEGIN {printf \"%.0f\", $disksize_bytes / 1073741824}")
              current_value="${disksize_gb}G"
            fi
            ;;
        esac
        ;;
      /etc/modules-load.d/*)
        # Check if module is loaded
        current_value="not loaded"
        local mod_name="$after_value"
        if lsmod 2>/dev/null | grep -q "^${mod_name} "; then
          current_value="loaded"
        fi
        ;;
      /usr/local/bin/run-in-*|/usr/local/bin/*)
        # Extract OOMScoreAdjust from helper script
        current_value="not present"
        if [[ -f "$config_file" ]]; then
          local oom_val
          oom_val=$(grep -oE 'OOMScoreAdjust=-?[0-9]+' "$config_file" 2>/dev/null | head -1 | cut -d= -f2)
          if [[ -n "$oom_val" ]]; then
            current_value="$oom_val"
          fi
        fi
        ;;
      *)
        if [[ -f "$config_file" ]]; then
          current_value="(file exists)"
        else
          current_value="(file missing)"
        fi
        ;;
    esac

    # Notes column
    local notes=""
    if [[ "$requires_reboot" == "true" ]]; then
      notes="[REBOOT]"
    fi

    # jemalloc skip check
    if [[ "$id" == "MEM-019" ]] && ! $has_jemalloc; then
      notes="${notes} [SKIP: library not found]"
    fi

    # Service existence check
    if [[ "$config_file" == "systemctl disable/mask" && "${svc_exists_cache[$parameter]:-no}" == "no" ]]; then
      notes="${notes} [SKIP: service not installed]"
    fi

    # CPU-003 is "keep active" — informational
    if [[ "$config_file" == "systemctl" && "$after_value" == *"kept"* ]]; then
      notes="${notes} [INFO: no change]"
    fi

    printf "%-12s %-42s %-40s %-20s → %-20s %s\n" \
      "$id" "${parameter:0:42}" "${config_file:0:40}" "${current_value:0:20}" "${target_value:0:20}" "$notes"

  done <<< "$tsv_data"

  echo ""
  echo "============================================"
  echo "  DRY RUN complete — no changes made"
  echo "  To apply: tune.sh --tier $TIER --apply"
  echo "============================================"
}

# -------------------------------------------------------
# APPLY: Actually apply tuning changes
# -------------------------------------------------------
do_apply() {
  local ram_kb
  ram_kb=$(detect_ram_kb)

  local filter
  filter=$(get_tier_filter)

  # Extract entries as TSV
  local tsv_data
  tsv_data=$(jq -r "[.tuning_entries[] | $filter] | .[] | [.id, .category, .parameter, .config_file, .before_value, .after_value, .risk, (.requires_reboot | tostring), (.scaling_note // \"\")] | @tsv" "$MANIFEST")

  local count
  count=$(echo "$tsv_data" | wc -l)

  echo "============================================"
  echo "  tunectl tune apply — Tier: $TIER"
  echo "============================================"
  echo ""
  echo "Applying $count entries..."
  echo ""

  # --- Step 1: Create timestamped backup ---
  local timestamp
  timestamp=$(date +%Y-%m-%dT%H-%M-%S)
  local backup_dir="${BACKUP_ROOT}/${timestamp}"
  mkdir -p "$backup_dir"
  echo "Backup directory: $backup_dir"

  # Collect unique config files that will be modified (exclude systemctl pseudo-files)
  local config_files_list
  config_files_list=$(echo "$tsv_data" | awk -F'\t' '{print $4}' | sort -u | grep -v '^systemctl')

  while IFS= read -r cf; do
    [[ -z "$cf" ]] && continue
    if [[ -f "$cf" ]]; then
      local backup_path="${backup_dir}${cf}"
      mkdir -p "$(dirname "$backup_path")"
      cp -p "$cf" "$backup_path"
      echo "  Backed up: $cf"
    fi
  done <<< "$config_files_list"
  echo ""

  # --- Step 2: Collect and write sysctl entries ---
  local sysctl_lines
  sysctl_lines=$(echo "$tsv_data" | awk -F'\t' '$4 == "/etc/sysctl.d/99-performance.conf"')
  local sysctl_count
  sysctl_count=$(echo "$sysctl_lines" | grep -c . || true)

  if [[ $sysctl_count -gt 0 ]]; then
    echo "Writing sysctl values to /etc/sysctl.d/99-performance.conf..."
    local sysctl_file="/etc/sysctl.d/99-performance.conf"

    {
      echo "# tunectl performance tuning — managed by tunectl"
      echo "# Tier: $TIER ($sysctl_count sysctl entries)"
      echo ""

      local prev_category=""
      while IFS=$'\t' read -r id category parameter config_file before_value after_value risk requires_reboot scaling_note; do
        local target_value
        target_value=$(scale_value "$id" "$after_value" "$ram_kb")

        if [[ "$category" != "$prev_category" ]]; then
          [[ -n "$prev_category" ]] && echo ""
          echo "# ${category} tuning"
          prev_category="$category"
        fi

        echo "$parameter = $target_value"
      done <<< "$sysctl_lines"
    } > "$sysctl_file"

    echo "  Written $sysctl_count sysctl parameters"

    if sysctl --system >/dev/null 2>&1; then
      echo "  Sysctl values reloaded"
    else
      echo "  Warning: sysctl reload returned non-zero (some params may have failed)" >&2
    fi
    echo ""
  fi

  # --- Step 3: Write other config files ---
  local grub_changed=false

  # Pre-check services
  local all_units
  all_units=$( (systemctl list-unit-files --type=service --no-legend 2>/dev/null; systemctl list-unit-files --type=socket --no-legend 2>/dev/null) | awk '{print $1}' || echo "")

  local has_jemalloc=false
  check_jemalloc && has_jemalloc=true

  while IFS=$'\t' read -r id category parameter config_file before_value after_value risk requires_reboot scaling_note; do
    local target_value
    target_value=$(scale_value "$id" "$after_value" "$ram_kb")

    case "$config_file" in
      /etc/sysctl.d/99-performance.conf)
        # Already handled above
        ;;

      "systemctl disable/mask")
        if echo "$all_units" | grep -qF "$parameter"; then
          systemctl disable "$parameter" 2>/dev/null || true
          systemctl mask "$parameter" 2>/dev/null || true
          systemctl stop "$parameter" 2>/dev/null || true
          echo "  $id: disabled/masked $parameter"
        else
          echo "  $id: SKIP — $parameter not installed"
        fi
        ;;

      "systemctl")
        echo "  $id: $parameter — no change (keep current state)"
        ;;

      /etc/environment)
        case "$id" in
          MEM-019)
            if $has_jemalloc; then
              if [[ -f /etc/environment ]] && grep -q '^LD_PRELOAD=' /etc/environment; then
                sed -i "s|^LD_PRELOAD=.*|LD_PRELOAD=$target_value|" /etc/environment
              else
                echo "LD_PRELOAD=$target_value" >> /etc/environment
              fi
              echo "  $id: Set LD_PRELOAD=$target_value"
            else
              echo "  $id: SKIP — jemalloc library not found"
            fi
            ;;
          BUILD-001)
            if command -v sccache &>/dev/null; then
              if [[ -f /etc/environment ]] && grep -q '^RUSTC_WRAPPER=' /etc/environment; then
                sed -i "s|^RUSTC_WRAPPER=.*|RUSTC_WRAPPER=$target_value|" /etc/environment
              else
                echo "RUSTC_WRAPPER=$target_value" >> /etc/environment
              fi
              echo "  $id: Set RUSTC_WRAPPER=$target_value"
            else
              echo "  $id: SKIP — sccache not found in PATH"
            fi
            ;;
        esac
        ;;

      /etc/default/grub)
        grub_changed=true
        handle_grub_entry "$id" "$parameter" "$target_value"
        ;;

      /etc/fstab)
        handle_fstab_entry "$id" "$parameter" "$target_value" "$ram_kb"
        ;;

      /etc/modules-load.d/*)
        mkdir -p "$(dirname "$config_file")"
        echo "$target_value" > "$config_file"
        echo "  $id: Written $config_file ($target_value)"
        ;;

      /etc/udev/rules.d/*)
        handle_udev_entry "$id" "$config_file" "$parameter" "$target_value"
        ;;

      /etc/tmpfiles.d/*)
        handle_tmpfiles_entry "$id" "$config_file" "$parameter" "$target_value"
        ;;

      /etc/systemd/system/*.slice)
        handle_slice_entry "$id" "$config_file" "$parameter" "$target_value"
        ;;

      /usr/local/bin/*)
        handle_helper_script "$id" "$config_file" "$parameter" "$target_value"
        ;;

      *)
        echo "  $id: Unknown config file type: $config_file (skipped)" >&2
        ;;
    esac
  done <<< "$tsv_data"

  # --- Step 4: Run update-grub if GRUB entries changed ---
  if $grub_changed; then
    echo ""
    echo "GRUB entries changed — running update-grub..."
    if command -v update-grub &>/dev/null; then
      update-grub 2>/dev/null || echo "  Warning: update-grub returned non-zero" >&2
    else
      echo "  Warning: update-grub not found" >&2
    fi
  fi

  # --- Step 5: Reload systemd if slice files changed ---
  systemctl daemon-reload 2>/dev/null || true

  echo ""
  echo "============================================"
  echo "  Apply complete — $count entries processed"
  echo "  Backup saved: $backup_dir"
  echo "============================================"
}

# -------------------------------------------------------
# Handle GRUB kernel command line entries
# -------------------------------------------------------
handle_grub_entry() {
  local id="$1"
  local parameter="$2"
  local target_value="$3"
  local grub_file="/etc/default/grub"

  if [[ ! -f "$grub_file" ]]; then
    echo "  $id: SKIP — $grub_file not found" >&2
    return
  fi

  local param_name="" param_str=""
  case "$id" in
    SWAP-009) param_name="zswap.enabled"; param_str="zswap.enabled=0" ;;
    BOOT-001) param_name="mitigations"; param_str="mitigations=auto,nosmt" ;;
    BOOT-002) param_name="l1tf"; param_str="l1tf=off" ;;
    BOOT-003) param_name="tsx_async_abort"; param_str="tsx_async_abort=off" ;;
    BOOT-004) param_name="preempt"; param_str="preempt=none" ;;
    BOOT-005) param_name="transparent_hugepage"; param_str="transparent_hugepage=always" ;;
    *)
      echo "  $id: Unknown GRUB parameter" >&2
      return
      ;;
  esac

  local current_cmdline
  current_cmdline=$(grep '^GRUB_CMDLINE_LINUX_DEFAULT=' "$grub_file" | sed 's/^GRUB_CMDLINE_LINUX_DEFAULT=//' | tr -d '"' || echo "")

  local new_cmdline
  new_cmdline=$(echo "$current_cmdline" | sed -E "s/(^| )${param_name}=[^ ]*//" | sed 's/  */ /g' | sed 's/^ //;s/ $//')

  if [[ -n "$new_cmdline" ]]; then
    new_cmdline="$new_cmdline $param_str"
  else
    new_cmdline="$param_str"
  fi

  if grep -q '^GRUB_CMDLINE_LINUX_DEFAULT=' "$grub_file"; then
    sed -i "s|^GRUB_CMDLINE_LINUX_DEFAULT=.*|GRUB_CMDLINE_LINUX_DEFAULT=\"$new_cmdline\"|" "$grub_file"
  else
    echo "GRUB_CMDLINE_LINUX_DEFAULT=\"$new_cmdline\"" >> "$grub_file"
  fi

  echo "  $id: GRUB $param_name set ($param_str)"
}

# -------------------------------------------------------
# Handle fstab entries
# -------------------------------------------------------
handle_fstab_entry() {
  local id="$1"
  local parameter="$2"
  local target_value="$3"
  local ram_kb="$4"
  local fstab="/etc/fstab"

  case "$id" in
    SWAP-005)
      if ! grep -q '/dev/zram0' "$fstab" 2>/dev/null; then
        echo "$target_value" >> "$fstab"
        echo "  $id: Added zram0 swap entry to fstab"
      else
        echo "  $id: zram0 already in fstab (idempotent)"
      fi
      ;;
    FS-001)
      if grep -qE '^\S+\s+/\s' "$fstab"; then
        if ! grep -E '^\S+\s+/\s' "$fstab" | grep -q 'noatime'; then
          sed -i -E '/^\S+\s+\/\s/s/(defaults[^ ]*)/\1,noatime/' "$fstab"
          echo "  $id: Added noatime to root mount"
        else
          echo "  $id: noatime already set (idempotent)"
        fi
      else
        echo "  $id: SKIP — root mount not found in fstab"
      fi
      ;;
    FS-002)
      if grep -qE '^\S+\s+/\s' "$fstab"; then
        if ! grep -E '^\S+\s+/\s' "$fstab" | grep -q 'commit='; then
          sed -i -E '/^\S+\s+\/\s/s/(defaults[^ ]*)/\1,commit=60/' "$fstab"
          echo "  $id: Added commit=60 to root mount"
        else
          echo "  $id: commit interval already set (idempotent)"
        fi
      else
        echo "  $id: SKIP — root mount not found in fstab"
      fi
      ;;
    FS-003)
      if ! grep -q 'tmpfs\s\+/tmp' "$fstab" 2>/dev/null; then
        echo "$target_value" >> "$fstab"
        echo "  $id: Added tmpfs /tmp entry"
      else
        echo "  $id: tmpfs /tmp already in fstab (idempotent)"
      fi
      ;;
    *)
      echo "  $id: Unknown fstab entry" >&2
      ;;
  esac
}

# -------------------------------------------------------
# Handle udev rule entries
# -------------------------------------------------------
handle_udev_entry() {
  local id="$1"
  local config_file="$2"
  local parameter="$3"
  local target_value="$4"

  mkdir -p "$(dirname "$config_file")"

  case "$id" in
    FS-004)
      echo 'ACTION=="add|change", KERNEL=="vd[a-z]|sd[a-z]|nvme[0-9]*", ATTR{queue/scheduler}="none"' > "$config_file"
      echo "  $id: Written I/O scheduler udev rule"
      ;;
    FS-005)
      local rule='ACTION=="add|change", KERNEL=="vd[a-z]|sd[a-z]|nvme[0-9]*", ATTR{queue/read_ahead_kb}="1024"'
      if [[ -f "$config_file" ]] && ! grep -q 'read_ahead_kb' "$config_file"; then
        echo "$rule" >> "$config_file"
      elif [[ ! -f "$config_file" ]]; then
        echo "$rule" > "$config_file"
      fi
      echo "  $id: Written read-ahead udev rule"
      ;;
    SWAP-006)
      local rule="KERNEL==\"zram0\", ATTR{comp_algorithm}=\"$target_value\""
      if [[ -f "$config_file" ]] && grep -q 'comp_algorithm' "$config_file"; then
        sed -i "s|comp_algorithm.*|comp_algorithm}=\"$target_value\"|" "$config_file"
      elif [[ -f "$config_file" ]]; then
        echo "$rule" >> "$config_file"
      else
        echo "$rule" > "$config_file"
      fi
      echo "  $id: Written zram comp_algorithm rule"
      ;;
    SWAP-007)
      local rule="KERNEL==\"zram0\", ATTR{disksize}=\"$target_value\""
      if [[ -f "$config_file" ]] && grep -q 'disksize' "$config_file"; then
        sed -i "s|disksize.*|disksize}=\"$target_value\"|" "$config_file"
      elif [[ -f "$config_file" ]]; then
        echo "$rule" >> "$config_file"
      else
        echo "$rule" > "$config_file"
      fi
      echo "  $id: Written zram disksize rule ($target_value)"
      ;;
    *)
      echo "  $id: Unhandled udev entry" >&2
      ;;
  esac
}

# -------------------------------------------------------
# Handle tmpfiles.d entries — manifest-driven, writes per-entry line
# Uses a tracking variable to write header + all entries on first call per file.
# -------------------------------------------------------
declare -A _tmpfiles_written=()

handle_tmpfiles_entry() {
  local id="$1"
  local config_file="$2"
  local parameter="$3"
  local target_value="$4"

  mkdir -p "$(dirname "$config_file")"

  # Write the complete file on first entry for this config_file.
  # All values are read from the manifest via the filtered tsv_data.
  if [[ -z "${_tmpfiles_written[$config_file]:-}" ]]; then
    _tmpfiles_written["$config_file"]=1

    # Determine sys path prefix and header based on file
    local header=""
    local sys_prefix=""
    case "$config_file" in
      /etc/tmpfiles.d/thp.conf)
        header="# Transparent Huge Pages configuration"
        sys_prefix="/sys/kernel/mm/"
        ;;
      /etc/tmpfiles.d/ksm.conf)
        header="# KSM (Kernel Samepage Merging) configuration"
        sys_prefix="/sys/kernel/mm/"
        ;;
    esac

    {
      echo "$header"
      # Extract all entries for this config_file from the manifest, apply scaling
      local file_entries
      file_entries=$(jq -r --arg cf "$config_file" \
        '[.tuning_entries[] | select(.config_file == $cf)] | .[] | [.id, .parameter, .after_value] | @tsv' \
        "$MANIFEST")
      local ram_kb
      ram_kb=$(detect_ram_kb)
      while IFS=$'\t' read -r eid eparam eafter; do
        [[ -z "$eid" ]] && continue
        local scaled_val
        scaled_val=$(scale_value "$eid" "$eafter" "$ram_kb")
        echo "w ${sys_prefix}${eparam} - - - - ${scaled_val}"
      done <<< "$file_entries"
    } > "$config_file"
    echo "  $id: Written ${config_file} (all entries from manifest)"
  else
    echo "  $id: ${config_file} already written"
  fi
}

# -------------------------------------------------------
# Handle systemd slice entries — manifest-driven, writes per-slice file
# Writes all properties for the slice from manifest on first call per file.
# -------------------------------------------------------
declare -A _slice_written=()

handle_slice_entry() {
  local id="$1"
  local config_file="$2"
  local parameter="$3"
  local target_value="$4"

  mkdir -p "$(dirname "$config_file")"

  # Write the complete slice file on first entry for this config_file.
  if [[ -z "${_slice_written[$config_file]:-}" ]]; then
    _slice_written["$config_file"]=1

    local ram_kb
    ram_kb=$(detect_ram_kb)

    {
      echo "[Slice]"
      # Extract all entries for this config_file from the manifest
      local file_entries
      file_entries=$(jq -r --arg cf "$config_file" \
        '[.tuning_entries[] | select(.config_file == $cf)] | .[] | [.id, .parameter, .after_value] | @tsv' \
        "$MANIFEST")
      while IFS=$'\t' read -r eid eparam eafter; do
        [[ -z "$eid" ]] && continue
        local scaled_val
        scaled_val=$(scale_value "$eid" "$eafter" "$ram_kb")
        # Extract the property name from the parameter field (e.g., "droid.slice CPUWeight" -> "CPUWeight")
        local prop_name
        prop_name=$(echo "$eparam" | awk '{print $NF}')
        echo "${prop_name}=${scaled_val}"
      done <<< "$file_entries"
    } > "$config_file"
    echo "  $id: Written ${config_file} (all entries from manifest)"
  else
    echo "  $id: ${config_file} already written"
  fi
}

# -------------------------------------------------------
# Handle helper scripts (run-in-droid, run-in-bulk) — manifest-driven
# OOMScoreAdjust value comes from the manifest's after_value field.
# -------------------------------------------------------
handle_helper_script() {
  local id="$1"
  local config_file="$2"
  local parameter="$3"
  local target_value="$4"

  mkdir -p "$(dirname "$config_file")"

  # Determine slice name from the config_file path
  local slice_name=""
  local script_desc=""
  case "$config_file" in
    */run-in-droid)
      slice_name="droid.slice"
      script_desc="Run a command in the droid.slice cgroup with OOM protection"
      ;;
    */run-in-bulk)
      slice_name="bulk.slice"
      script_desc="Run a command in the bulk.slice cgroup (OOM-expendable)"
      ;;
    *)
      echo "  $id: Unknown helper script: $config_file" >&2
      return
      ;;
  esac

  cat > "$config_file" <<SCRIPT
#!/usr/bin/env bash
# $(basename "$config_file"): ${script_desc}
exec systemd-run --slice=${slice_name} --scope --property=OOMScoreAdjust=${target_value} -- "\$@"
SCRIPT
  chmod +x "$config_file"
  echo "  $id: Written $config_file (OOMScoreAdjust=$target_value)"
}

# -------------------------------------------------------
# Main
# -------------------------------------------------------
main() {
  parse_args "$@"
  validate_manifest

  if [[ "$MODE" == "dry-run" ]]; then
    do_dry_run
  else
    do_apply
  fi

  exit 0
}

main "$@"
