#!/bin/sh
set -eu

REPO_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
TARGET_USER=${TARGET_USER:-user}
TARGET_UID=${TARGET_UID:-1000}
STATE_DIR=/var/lib/system-monitor
DRY_RUN=0
TARGET_ENTRY=$(getent passwd "$TARGET_USER")
if [ -z "$TARGET_ENTRY" ]; then
  echo "cannot resolve account for $TARGET_USER" >&2
  exit 1
fi
resolved_uid=$(printf '%s\n' "$TARGET_ENTRY" | cut -d: -f3)
TARGET_HOME=$(printf '%s\n' "$TARGET_ENTRY" | cut -d: -f6)
TARGET_GID=$(printf '%s\n' "$TARGET_ENTRY" | cut -d: -f4)
if [ "$resolved_uid" != "$TARGET_UID" ] || [ -z "$TARGET_GID" ] || [ -z "$TARGET_HOME" ] || [ "$TARGET_HOME" = / ]; then
  echo "account mismatch for $TARGET_USER: expected uid $TARGET_UID, got uid ${resolved_uid:-missing}, home ${TARGET_HOME:-missing}" >&2
  exit 1
fi

for arg in "$@"; do
  case "$arg" in
    --dry-run) DRY_RUN=1 ;;
    -*) echo "unknown flag: $arg" >&2; exit 1 ;;
    *) echo "unknown argument: $arg" >&2; exit 1 ;;
  esac
done

# run <verb> <target> <command...> — exactly two label words, then the command.
# A third label word silently shifts into argv[0] and the step dies as
# "not found", so the label is validated instead of trusted.
run() {
  verb=$1
  target=$2
  shift 2
  if [ "$DRY_RUN" != 1 ] && ! command -v "$1" >/dev/null 2>&1; then
    echo "install.sh bug: 'run $verb $target' names '$1' as its command, which is not executable" >&2
    exit 1
  fi
  if [ "$DRY_RUN" = 1 ]; then
    echo "DRY-RUN: $verb $target"
    return 0
  fi
  "$@"
}

need_root() {
  if [ "$DRY_RUN" = 1 ]; then
    return 0
  fi
  if [ "$(id -u)" != 0 ]; then
    echo "install.sh must run as root through gui-askpass sudo" >&2
    exit 1
  fi
}

install_file() {
  src=$1 dst=$2 mode=$3 owner=${4:-root:root}
  run install "$dst" install -d -m 0755 "$(dirname "$dst")"
  run install "$dst" install -m "$mode" -o "${owner%:*}" -g "${owner#*:}" "$src" "$dst"
  if [ "$DRY_RUN" != 1 ]; then
    echo "wrote $dst"
  fi
}

safe_user_directory() {
  relative=$1
  run mkdir "$TARGET_HOME/$relative" python3 "$REPO_ROOT/lib/user-home-files.py" ensure-directory \
    --home "$TARGET_HOME" --relative "$relative" --uid "$TARGET_UID" --gid "$TARGET_GID"
}

safe_user_link() {
  src=$1 relative=$2
  run link "$TARGET_HOME/$relative" python3 "$REPO_ROOT/lib/user-home-files.py" symlink \
    --home "$TARGET_HOME" --relative "$relative" --target "$src" --uid "$TARGET_UID" --gid "$TARGET_GID"
  if [ "$DRY_RUN" != 1 ]; then
    echo "linked $TARGET_HOME/$relative -> $src"
  fi
}

safe_user_unlink() {
  relative=$1 expected=${2:-}
  if [ -n "$expected" ]; then
    run rm "$TARGET_HOME/$relative" python3 "$REPO_ROOT/lib/user-home-files.py" unlink \
      --home "$TARGET_HOME" --relative "$relative" --target "$expected" \
      --uid "$TARGET_UID" --gid "$TARGET_GID"
  else
    run rm "$TARGET_HOME/$relative" python3 "$REPO_ROOT/lib/user-home-files.py" unlink \
      --home "$TARGET_HOME" --relative "$relative" --uid "$TARGET_UID" --gid "$TARGET_GID"
  fi
}

# User units are symlinked to the repo body, matching `deckctl units apply`.
# Copying here would replace that link and let the live unit drift from the repo.
link_user_unit() {
  src=$1 dst=$2
  relative=${dst#"$TARGET_HOME"/}
  parent=${relative%/*}
  safe_user_directory "$parent"
  safe_user_link "$src" "$relative"
}

write_file() {
  dst=$1 mode=$2 owner=${3:-root:root}
  if [ "$DRY_RUN" = 1 ]; then
    run write "$dst" true
    return 0
  fi
  tmp=$(mktemp)
  cat > "$tmp"
  install -d -m 0755 "$(dirname "$dst")"
  install -m "$mode" -o "${owner%:*}" -g "${owner#*:}" "$tmp" "$dst"
  rm -f "$tmp"
  echo "wrote $dst"
}

deploy_agent_guard() {
  run mkdir /opt/system-monitor install -d -m 0755 /opt/system-monitor
  run rm /opt/system-monitor/agent-guard rm -rf /opt/system-monitor/agent-guard
  run cp /opt/system-monitor/agent-guard cp -a "$REPO_ROOT/agent-guard" /opt/system-monitor/agent-guard
  run chown /opt/system-monitor/agent-guard chown -R root:root /opt/system-monitor/agent-guard
  install_file "$REPO_ROOT/agent-guard/config/agent-guard.toml" /etc/system-monitor/agent-guard.toml 0644
  write_file /usr/local/bin/agent-guard 0755 <<'EOF'
#!/bin/sh
export PYTHONPATH=/opt/system-monitor/agent-guard/src
exec python3 -m agent_guard.daemon "$@"
EOF
  write_file /usr/local/bin/agentctl 0755 <<'EOF'
#!/bin/sh
export PYTHONPATH=/opt/system-monitor/agent-guard/src
exec python3 -m agent_guard.cli "$@"
EOF
  user_home=$TARGET_HOME
  safe_user_directory ".config/systemd/user"
  link_user_unit "$REPO_ROOT/systemd/user/agent-guard.service" "$user_home/.config/systemd/user/agent-guard.service"
  run loginctl "linger $TARGET_USER" loginctl enable-linger "$TARGET_USER"
  run systemctl user-daemon-reload runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$TARGET_UID" systemctl --user daemon-reload
  run systemctl "enable agent-guard.service" runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$TARGET_UID" systemctl --user enable --now agent-guard.service
}

deploy_root_fixes() {
  for f in "$REPO_ROOT"/slices/systemd/*.slice; do
    install_file "$f" "/etc/systemd/system/$(basename "$f")" 0644
  done
  install_file "$REPO_ROOT/slices/bin/run-agent" /usr/local/bin/run-agent 0755
  install_file "$REPO_ROOT/slices/bin/run-build" /usr/local/bin/run-build 0755
  install_file "$REPO_ROOT/fixes/tmpfiles.d/system-monitor-tmp.conf" /etc/tmpfiles.d/system-monitor-tmp.conf 0644
  run systemctl daemon-reload systemctl daemon-reload
  run systemd-tmpfiles /etc/tmpfiles.d/system-monitor-tmp.conf systemd-tmpfiles --clean /etc/tmpfiles.d/system-monitor-tmp.conf
}

JOURNAL_CAP=5G
# systemd reads drop-ins in lexical order and the last wins, so these two — which
# predate this module and disagree with each other (1G vs 500M) — would override
# 90-journal-retention.conf wherever they survive.
SUPERSEDED_JOURNALD_DROPINS="persistent.conf size.conf"

deploy_journal_retention() {
  install_file "$REPO_ROOT/journald.conf.d/90-journal-retention.conf" \
    /etc/systemd/journald.conf.d/90-journal-retention.conf 0644
  for name in $SUPERSEDED_JOURNALD_DROPINS; do
    if [ -e "/etc/systemd/journald.conf.d/$name" ]; then
      run rm "/etc/systemd/journald.conf.d/$name" rm -f "/etc/systemd/journald.conf.d/$name"
    fi
  done
  run systemctl "restart systemd-journald" systemctl restart systemd-journald
  if [ "$DRY_RUN" != 1 ]; then
    effective=$(grep -hs '^SystemMaxUse=' /etc/systemd/journald.conf /etc/systemd/journald.conf.d/*.conf 2>/dev/null | tail -1)
    if [ "$effective" != "SystemMaxUse=$JOURNAL_CAP" ]; then
      echo "verify: effective journald SystemMaxUse is '${effective:-unset}', not $JOURNAL_CAP" >&2
      exit 1
    fi
    echo "verify: journald retention floor is $JOURNAL_CAP / 30day"
  fi
}

retire_old_notifiers() {
  if [ "$DRY_RUN" = 1 ]; then
    python3 "$REPO_ROOT/lib/notifier-retirement.py" retire \
      --user "$TARGET_USER" --uid "$TARGET_UID" --home "$TARGET_HOME" \
      --state-dir "$STATE_DIR" --dry-run
  else
    python3 "$REPO_ROOT/lib/notifier-retirement.py" retire \
      --user "$TARGET_USER" --uid "$TARGET_UID" --home "$TARGET_HOME" \
      --state-dir "$STATE_DIR"
  fi
}

deploy_desktop_survivability() {
  user_home=$TARGET_HOME
  safe_user_directory ".local/bin"
  for b in pids-guard pids-rescue desktop-memory-reserve cinnamon-safe-recover; do
    safe_user_link "$REPO_ROOT/bin/$b" ".local/bin/$b"
  done
  run link "$user_home/.local/bin/system-monitor-disk-check" ln -sfn "$REPO_ROOT/slices/bin/disk-check" "$user_home/.local/bin/system-monitor-disk-check"
  run chown "$user_home/.local/bin/system-monitor-disk-check" chown -h "$TARGET_USER:$TARGET_USER" "$user_home/.local/bin/system-monitor-disk-check"
  run link "$user_home/.local/bin/system-monitor-disk-maintain" ln -sfn "$REPO_ROOT/slices/bin/disk-maintain" "$user_home/.local/bin/system-monitor-disk-maintain"
  run chown "$user_home/.local/bin/system-monitor-disk-maintain" chown -h "$TARGET_USER:$TARGET_USER" "$user_home/.local/bin/system-monitor-disk-maintain"
  link_user_unit "$REPO_ROOT/systemd/user/guard.slice" "$user_home/.config/systemd/user/guard.slice"
  link_user_unit "$REPO_ROOT/systemd/user/pids-guard.service" "$user_home/.config/systemd/user/pids-guard.service"
  link_user_unit "$REPO_ROOT/systemd/user/desktop-memory-reserve.service" "$user_home/.config/systemd/user/desktop-memory-reserve.service"
  link_user_unit "$REPO_ROOT/systemd/user/system-monitor-disk-check.service" "$user_home/.config/systemd/user/system-monitor-disk-check.service"
  link_user_unit "$REPO_ROOT/systemd/user/system-monitor-disk-check.timer" "$user_home/.config/systemd/user/system-monitor-disk-check.timer"
  for d in 50-TasksMax.conf override.conf 50-oomd-omit.conf; do
    link_user_unit "$REPO_ROOT/systemd/user/app.slice.d/$d" "$user_home/.config/systemd/user/app.slice.d/$d"
  done
  # The 15s timer is superseded by the watch daemon; absent on a fresh host.
  run systemctl "disable pids-guard.timer" sh -c "runuser -u '$TARGET_USER' -- env XDG_RUNTIME_DIR='/run/user/$TARGET_UID' systemctl --user disable --now pids-guard.timer || true"
  safe_user_unlink ".config/systemd/user/pids-guard.timer" "$REPO_ROOT/systemd/user/pids-guard.timer"
  run systemctl user-daemon-reload runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$TARGET_UID" systemctl --user daemon-reload
  run systemctl "enable pids-guard.service" runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$TARGET_UID" systemctl --user enable --now pids-guard.service
  run systemctl "enable desktop-memory-reserve.service" runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$TARGET_UID" systemctl --user enable --now desktop-memory-reserve.service
  run systemctl "enable system-monitor-disk-check.timer" runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$TARGET_UID" systemctl --user enable --now system-monitor-disk-check.timer
}

as_target_user() {
  runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$TARGET_UID" "$@"
}

verify_guards() {
  if [ "$DRY_RUN" = 1 ]; then
    return 0
  fi
  user_home=$TARGET_HOME
  fail=0
  if [ "$(as_target_user systemctl --user is-active pids-guard.service)" != active ]; then
    echo "verify: pids-guard.service is not active" >&2
    fail=1
  fi
  if [ "$(as_target_user systemctl --user show guard.slice -p TasksMax --value)" != infinity ]; then
    echo "verify: guard.slice TasksMax is not infinity" >&2
    fail=1
  fi
  for p in "$user_home/.local/bin/pids-guard" "$user_home/.local/bin/pids-rescue" \
           "$user_home/.local/bin/system-monitor-disk-check" \
           "$user_home/.local/bin/system-monitor-disk-maintain" \
           "$user_home/.config/systemd/user/pids-guard.service" \
           "$user_home/.config/systemd/user/guard.slice" \
           "$user_home/.config/systemd/user/system-monitor-disk-check.service" \
           "$user_home/.config/systemd/user/system-monitor-disk-check.timer"; do
    resolved=$(readlink -f "$p" 2>/dev/null || true)
    case "$resolved" in
      "$REPO_ROOT"/?*) ;;
      *) echo "verify: $p resolves to '$resolved', not under $REPO_ROOT" >&2; fail=1 ;;
    esac
  done
  if [ "$DRY_RUN" != 1 ]; then
    if [ "$(as_target_user systemctl --user is-enabled system-monitor-disk-check.timer 2>/dev/null || true)" != enabled ]; then
      echo "verify: system-monitor-disk-check.timer is not enabled" >&2
      fail=1
    fi
  fi
  if [ "$fail" != 0 ]; then
    exit 1
  fi
  echo "verify: pid guard armed from $REPO_ROOT"
}

# The pid guard arms first: under `set -eu` anything ordered ahead of it can
# abort the run and leave the machine with no protection against fork storms.
need_root
deploy_desktop_survivability
deploy_agent_guard
deploy_root_fixes
deploy_desktop_survivability
deploy_journal_retention
retire_old_notifiers
verify_guards
if [ "$DRY_RUN" = 1 ]; then
  echo "DRY-RUN: complete system-monitor install"
else
  echo "system-monitor install complete"
fi
