#!/bin/sh
# Clawdie Shell — System Configuration Module
# Purpose: System-level config (rc.conf, hostname, services, environment)
# Author: Clawdie Project
# POSIX-compliant (no bash-isms)

set -eu
# FreeBSD /bin/sh doesn't support trap ERR

# Configuration (can be overridden for testing)
RC_CONF="${RC_CONF:-/etc/rc.conf}"
LOADER_CONF="${LOADER_CONF:-/boot/loader.conf}"
SYSCTL_CONF="${SYSCTL_CONF:-/etc/sysctl.conf}"
HOSTNAME_FILE="${HOSTNAME_FILE:-/etc/hostname}"
PROFILE_DIR="${PROFILE_DIR:-/etc/profile.d}"
LOG_FILE="${LOG_FILE:-/var/log/clawdie-firstboot.log}"
PROGRESS_FILE="${PROGRESS_FILE:-/var/log/clawdie-firstboot.progress}"

# Derived from wizard inputs (caller sets these)
# TZ                - Timezone (e.g., "Europe/Ljubljana")
# AGENT_DOMAIN      - FQDN (e.g., "clawdie.home.arpa" for local, or public domain)
# DETECTED_GPU      - GPU vendor from gpu module (intel, amd, nvidia, vmware, vesa)

# ============================================================================
# MAIN ENTRY POINT
# ============================================================================

clawdie_shell_system_config() {
  # Main orchestrator

  log_msg "[system] Starting system configuration"

  if [ -z "${TZ:-}" ]; then
    log_msg "[system] ERROR: TZ not set"
    return 1
  fi

  if [ -z "${AGENT_DOMAIN:-}" ]; then
    log_msg "[system] ERROR: AGENT_DOMAIN not set"
    return 1
  fi

  if [ -z "${SYSTEM_LOCALE:-}" ]; then
    log_msg "[system] ERROR: SYSTEM_LOCALE not set"
    return 1
  fi

  # Write rc.conf with timezone and services
  clawdie_shell_system_write_rcconf
  log_msg "[system] Updated rc.conf"

  # Set hostname
  clawdie_shell_system_set_hostname
  log_msg "[system] Set hostname"

  # Setup environment
  clawdie_shell_system_setup_env
  log_msg "[system] Setup environment"

  # Setup locale + keymap
  clawdie_shell_system_setup_locale
  log_msg "[system] Setup locale"

  # Enable FreeBSD mac_do framework with no credential grants yet
  clawdie_shell_system_setup_mac_do
  log_msg "[system] Setup mac_do framework"

  # Enable evdev input routing for Xorg/libinput touchpads and mice
  clawdie_shell_system_setup_evdev
  log_msg "[system] Setup evdev input routing"

  # Enable Linux compatibility at boot and best-effort for the current boot
  clawdie_shell_system_setup_linuxulator
  log_msg "[system] Setup Linux compatibility"

  # Enable a baseline bhyve host configuration on the target
  clawdie_shell_system_setup_bhyve
  log_msg "[system] Setup bhyve host baseline"

  # Enable services
  clawdie_shell_system_enable_services
  log_msg "[system] Enabled services"

  echo "[SYSTEM] COMPLETE" >> "$PROGRESS_FILE"
  log_msg "[system] System configuration complete"
}

# ============================================================================
# RC.CONF CONFIGURATION
# ============================================================================

clawdie_shell_system_write_rcconf() {
  # Update /etc/rc.conf with:
  # - timezone
  # - service configurations (dbus, sddm)
  # - desktop settings

  if [ ! -f "$RC_CONF" ]; then
    log_msg "[system] Creating $RC_CONF"
    touch "$RC_CONF"
  fi

  # Helper to set or update rc.conf variable
  clawdie_shell_system_sysrc "timezone=$TZ"
  if [ -n "${KEYMAP:-}" ]; then
    clawdie_shell_system_sysrc "keymap=$KEYMAP"
  fi
  clawdie_shell_system_sysrc "dbus_enable=YES"
  clawdie_shell_system_sysrc "display_manager=sddm"
  clawdie_shell_system_sysrc "sddm_enable=YES"
  clawdie_shell_system_sysrc "linux_enable=YES"
  clawdie_shell_system_sysrc "zfs_enable=YES"
  clawdie_shell_system_sysrc "vm_enable=YES"
  clawdie_shell_system_append_rc_list kld_list linux linux64 zfs vmm nmdm if_tap if_bridge

  log_msg "[system] Wrote rc.conf configuration"
}

clawdie_shell_system_sysrc() {
  # Add or update a variable in rc.conf
  # Input: VAR=VALUE

  clawdie_shell_system_set_config_line "$RC_CONF" "$1"
}

clawdie_shell_system_set_config_line() {
  # Add or update KEY=VALUE in a config file.
  # Input: FILE KEY=VALUE

  local config_file="$1"
  local var_assignment="$2"
  local var_name

  var_name=$(echo "$var_assignment" | cut -d= -f1)

  if [ ! -f "$config_file" ]; then
    mkdir -p "$(dirname "$config_file")"
    touch "$config_file"
  fi

  if grep -q "^${var_name}=" "$config_file" 2>/dev/null; then
    sed -i '' "s|^${var_name}=.*|${var_assignment}|" "$config_file"
  else
    echo "$var_assignment" >> "$config_file"
  fi
}

clawdie_shell_system_append_rc_list() {
  # Append values to a quoted rc.conf list variable without duplicating entries.
  # Input: VAR VALUE [VALUE...]

  local var_name="$1"
  shift
  local current merged value

  current=$(sed -n "s/^${var_name}=\"\\(.*\\)\"/\\1/p" "$RC_CONF" 2>/dev/null | head -1)
  merged="$current"

  for value in "$@"; do
    if [ -z "$merged" ]; then
      merged="$value"
      continue
    fi
    case " ${merged} " in
      *" ${value} "*) ;;
      *)
        merged="${merged} ${value}"
        ;;
    esac
  done

  clawdie_shell_system_set_config_line "$RC_CONF" "${var_name}=\"${merged}\""
}

# ============================================================================
# MAC_DO FRAMEWORK
# ============================================================================

clawdie_shell_system_setup_mac_do() {
  # Load FreeBSD mac_do at boot, but grant no credential transitions yet.
  # FreeBSD 15 accepts an empty rule list as no-op policy: security.mac.do.rules=

  clawdie_shell_system_set_config_line "$LOADER_CONF" 'mac_do_load="YES"'
  clawdie_shell_system_set_config_line "$SYSCTL_CONF" 'security.mac.do.rules='

  # Best-effort live activation for the firstboot session. The persistent
  # loader/sysctl files above are the source of truth after reboot.
  if command -v kldload >/dev/null 2>&1; then
    kldload mac_do 2>/dev/null || true
  elif [ -x /sbin/kldload ]; then
    /sbin/kldload mac_do 2>/dev/null || true
  fi

  if command -v sysctl >/dev/null 2>&1; then
    sysctl security.mac.do.rules= >/dev/null 2>&1 || true
  elif [ -x /sbin/sysctl ]; then
    /sbin/sysctl security.mac.do.rules= >/dev/null 2>&1 || true
  fi
}

# ============================================================================
# EVDEV INPUT ROUTING
# ============================================================================

clawdie_shell_system_setup_evdev() {
  # FreeBSD's xorg/libinput stack recommends routing native evdev events for
  # ums(4)/psm(4). This improves touchpad and mouse support and avoids PS/2
  # detection issues when moused is disabled.
  clawdie_shell_system_set_config_line "$SYSCTL_CONF" 'kern.evdev.rcpt_mask=6'

  if command -v sysctl >/dev/null 2>&1; then
    sysctl kern.evdev.rcpt_mask=6 >/dev/null 2>&1 || true
  elif [ -x /sbin/sysctl ]; then
    /sbin/sysctl kern.evdev.rcpt_mask=6 >/dev/null 2>&1 || true
  fi
}

# ============================================================================
# LINUX COMPATIBILITY
# ============================================================================

clawdie_shell_system_setup_linuxulator() {
  # Persist Linux compatibility in rc.conf and try to activate it immediately.
  # This runs on the target system during firstboot, not on the ISO build host.

  local module

  for module in linux64 linux; do
    if command -v kldload >/dev/null 2>&1; then
      kldload "$module" >/dev/null 2>&1 || true
    elif [ -x /sbin/kldload ]; then
      /sbin/kldload "$module" >/dev/null 2>&1 || true
    fi
  done

  if command -v service >/dev/null 2>&1; then
    service linux onestart >/dev/null 2>&1 || true
  elif [ -x /usr/sbin/service ]; then
    /usr/sbin/service linux onestart >/dev/null 2>&1 || true
  fi
}

# ============================================================================
# BHYVE HOST BASELINE
# ============================================================================

clawdie_shell_system_setup_bhyve() {
  # Persist core bhyve modules and tap behavior. Leave bridge/vm_dir creation
  # to later host-specific setup because the correct NIC and storage dataset are
  # machine-dependent.

  local module

  clawdie_shell_system_set_config_line "$SYSCTL_CONF" 'net.link.tap.up_on_open=1'

  if command -v sysctl >/dev/null 2>&1; then
    sysctl net.link.tap.up_on_open=1 >/dev/null 2>&1 || true
  elif [ -x /sbin/sysctl ]; then
    /sbin/sysctl net.link.tap.up_on_open=1 >/dev/null 2>&1 || true
  fi

  for module in vmm nmdm if_tap if_bridge; do
    if command -v kldload >/dev/null 2>&1; then
      kldload "$module" >/dev/null 2>&1 || true
    elif [ -x /sbin/kldload ]; then
      /sbin/kldload "$module" >/dev/null 2>&1 || true
    fi
  done
}

# ============================================================================
# HOSTNAME CONFIGURATION
# ============================================================================

clawdie_shell_system_set_hostname() {
  # Set /etc/hostname and apply live

  if [ ! -f "$HOSTNAME_FILE" ]; then
    touch "$HOSTNAME_FILE"
  fi

  # Write to file
  echo "$AGENT_DOMAIN" > "$HOSTNAME_FILE"

  # Apply live (if not in chroot)
  if command -v hostname >/dev/null 2>&1; then
    hostname "$AGENT_DOMAIN" 2>/dev/null || true
  fi

  log_msg "[system] Set hostname to $AGENT_DOMAIN"
}

# ============================================================================
# ENVIRONMENT SETUP
# ============================================================================

clawdie_shell_system_setup_env() {
  # Create /etc/profile.d/clawdie.sh for environment variables
  # Sets up npm global paths and other Clawdie-specific variables

  if [ ! -d "$PROFILE_DIR" ]; then
    mkdir -p "$PROFILE_DIR"
  fi

  local clawdie_profile="$PROFILE_DIR/clawdie.sh"
  local normalized_locale
  normalized_locale=$(clawdie_shell_system_normalize_locale "$SYSTEM_LOCALE")

  cat > "$clawdie_profile" <<EOF
# Clawdie-AI environment setup
# Adds npm global bin directory to PATH

_clawdie_npm_prefix="/opt/clawdie/npm-global"
_clawdie_base_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
_clawdie_path_has() {
  case ":\${PATH:-}:" in
    *:"\$1":*) return 0 ;;
    *) return 1 ;;
  esac
}
export npm_config_prefix="\${_clawdie_npm_prefix}"
export NPM_CONFIG_PREFIX="\${_clawdie_npm_prefix}"
export NPM_CONFIG_UPDATE_NOTIFIER=false
export NO_UPDATE_NOTIFIER=1
if [ -z "\${PATH:-}" ]; then
  PATH="\${_clawdie_base_path}"
else
  for _clawdie_dir in /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin; do
    _clawdie_path_has "\${_clawdie_dir}" || PATH="\${PATH}:\${_clawdie_dir}"
  done
fi
_clawdie_path_has "\${_clawdie_npm_prefix}/bin" || PATH="\${_clawdie_npm_prefix}/bin:\${PATH}"
export PATH
unset _clawdie_npm_prefix
unset _clawdie_base_path
unset -f _clawdie_path_has 2>/dev/null || true
export LANG="${normalized_locale}"
export LC_ALL="${normalized_locale}"
EOF

  chmod 644 "$clawdie_profile"

  if id clawdie >/dev/null 2>&1; then
    if [ -x /usr/local/bin/bash ]; then
      if ! grep -qx '/usr/local/bin/bash' /etc/shells 2>/dev/null; then
        echo '/usr/local/bin/bash' >> /etc/shells 2>/dev/null || true
      fi
      pw usermod clawdie -s /usr/local/bin/bash 2>/dev/null || true
    fi
    if [ -x /usr/local/bin/zsh ] && ! grep -qx '/usr/local/bin/zsh' /etc/shells 2>/dev/null; then
      echo '/usr/local/bin/zsh' >> /etc/shells 2>/dev/null || true
    fi

    cat > /home/clawdie/.profile <<'EOF'
# Clawdie operator POSIX shell profile.
[ -r /etc/profile.d/clawdie.sh ] && . /etc/profile.d/clawdie.sh

# Start SSH agent on login (FreeBSD: no systemd, no X11 agent launcher).
# Non-login shells (tmux windows) use ~/.bashrc fallback instead.
if [ -z "$SSH_AUTH_SOCK" ]; then
  eval $(ssh-agent -s) > /dev/null 2>&1
fi
EOF
    cat > /home/clawdie/.bash_profile <<'EOF'
# Clawdie operator bash login profile.
[ -r /etc/profile ] && . /etc/profile
[ -L "${HOME}/.cache" ] && {
  mkdir -p /tmp/clawdie/cache 2>/dev/null || true
  chown "$(id -u):$(id -g)" /tmp/clawdie /tmp/clawdie/cache 2>/dev/null || true
  chmod 0700 /tmp/clawdie /tmp/clawdie/cache 2>/dev/null || true
}
[ -r "${HOME}/.bashrc" ] && . "${HOME}/.bashrc"
EOF
    cat > /home/clawdie/.bashrc <<'BASHRC'
# Clawdie operator interactive bash shell.
# Non-interactive shells stop here.
case $- in *i*) ;; *) return ;; esac

# ── PATH (FreeBSD: /usr/local first) ──────────────────────────
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
[ -r /etc/profile.d/clawdie.sh ] && . /etc/profile.d/clawdie.sh

# ── Privilege escalation ──────────────────────────────────────
# Clawdie ISO: no sudo pkg. Use FreeBSD mac_do instead.
# mac_do rules (gid=0>uid=0) allow wheel→root transitions.
# ZFS auto-snapshot provides safety net — no confirmation prompts.
sudo() {
  local pool
  pool=$(zpool list -H -o name 2>/dev/null | head -1)
  if [ -n "$pool" ]; then
    (zfs snapshot "${pool}@cli-$(date +%s)" 2>/dev/null &)
  fi
  mdo -u root "$@"
}

# ── SSH agent (fallback for non-login shells) ─────────────────
# .profile starts the agent for login shells; tmux windows
# (non-login) inherit via SSH_AUTH_SOCK if the parent had one,
# but if not, try a stored env file from a prior login.
if [ -z "$SSH_AUTH_SOCK" ] && [ -f ~/.ssh-agent-env ]; then
  . ~/.ssh-agent-env 2>/dev/null || true
fi

# ── Prompt ────────────────────────────────────────────────────
PS1='\h:\w\$ '

# ── History ───────────────────────────────────────────────────
export HISTFILE="${HISTFILE:-/tmp/clawdie/bash_history}"
export HISTSIZE=5000
export HISTFILESIZE=10000
mkdir -p /tmp/clawdie 2>/dev/null || true

# ── Aliases ───────────────────────────────────────────────────
alias ll='ls -lah'
alias la='ls -A'
alias lt='ls -laht'
BASHRC
    cat > /home/clawdie/.zprofile <<'EOF'
# Clawdie operator zsh login profile.
[ -r /etc/profile ] && . /etc/profile
[ -L "${HOME}/.cache" ] && {
  mkdir -p /tmp/clawdie/cache 2>/dev/null || true
  chown "$(id -u):$(id -g)" /tmp/clawdie /tmp/clawdie/cache 2>/dev/null || true
  chmod 0700 /tmp/clawdie /tmp/clawdie/cache 2>/dev/null || true
}
[ -r /etc/profile.d/clawdie.sh ] && . /etc/profile.d/clawdie.sh
[ -r "${HOME}/.zshrc" ] && . "${HOME}/.zshrc"
EOF
    cat > /home/clawdie/.zshrc <<'EOF'
# Clawdie operator interactive zsh profile.
[ -r /etc/profile.d/clawdie.sh ] && . /etc/profile.d/clawdie.sh
export HISTFILE="${HISTFILE:-/tmp/clawdie/zsh_history}"
mkdir -p /tmp/clawdie 2>/dev/null || true

# Keep zsh optional, but ready: use packaged oh-my-zsh when present.
if [ -d /usr/local/share/ohmyzsh ]; then
    export ZSH="/usr/local/share/ohmyzsh"
elif [ -d /usr/local/share/oh-my-zsh ]; then
    export ZSH="/usr/local/share/oh-my-zsh"
fi
if [ -n "${ZSH:-}" ] && [ -r "${ZSH}/oh-my-zsh.sh" ]; then
    ZSH_THEME="${ZSH_THEME:-robbyrussell}"
    plugins=(git)
    source "${ZSH}/oh-my-zsh.sh"
fi
EOF
    if [ -f /usr/local/etc/xdg/tumbler/tumbler.rc ]; then
      mkdir -p /home/clawdie/.config/tumbler
      cp /usr/local/etc/xdg/tumbler/tumbler.rc /home/clawdie/.config/tumbler/tumbler.rc
    fi
    chown -R clawdie:clawdie /home/clawdie/.config/tumbler 2>/dev/null || true
    chown clawdie:clawdie /home/clawdie/.profile /home/clawdie/.bash_profile /home/clawdie/.bashrc /home/clawdie/.zprofile /home/clawdie/.zshrc 2>/dev/null || true
    chmod 644 /home/clawdie/.profile /home/clawdie/.bash_profile /home/clawdie/.bashrc /home/clawdie/.zprofile /home/clawdie/.zshrc 2>/dev/null || true
    chmod 644 /home/clawdie/.config/tumbler/tumbler.rc 2>/dev/null || true
  fi

  log_msg "[system] Created $clawdie_profile"
}

# ============================================================================
# SERVICE ENABLEMENT
# ============================================================================

clawdie_shell_system_enable_services() {
  # Enable and start required services
  # Safe to fail if running in chroot (first boot)

  local services="dbus sddm"

  for service in $services; do
    if command -v service >/dev/null 2>&1; then
      # Try to start service
      service "$service" onestart 2>/dev/null || {
        log_msg "[system] Could not start $service (expected in chroot)"
      }
    fi
  done

  log_msg "[system] Service enablement complete"
}

# ============================================================================
# LOCALE + KEYMAP SETUP
# ============================================================================

clawdie_shell_system_setup_locale() {
  local normalized_locale
  normalized_locale=$(clawdie_shell_system_normalize_locale "$SYSTEM_LOCALE")

  # Write per-user login_conf (must be UTF-8).
  if id clawdie >/dev/null 2>&1; then
    local login_conf="/home/clawdie/.login_conf"
    cat > "$login_conf" <<EOF
me:\\
	:charset=UTF-8:\\
	:lang=${normalized_locale}:
EOF
    chown clawdie:clawdie "$login_conf" 2>/dev/null || true
    chmod 644 "$login_conf" 2>/dev/null || true
    if command -v cap_mkdb >/dev/null 2>&1; then
      cap_mkdb "$login_conf" 2>/dev/null || true
    fi
  fi
}

clawdie_shell_system_normalize_locale() {
  local raw="$1"
  raw=$(echo "$raw" | tr '-' '_')
  raw=${raw%%.*}
  raw=${raw%%@*}
  if [ -z "$raw" ]; then
    raw="C"
  fi
  echo "${raw}.UTF-8"
}

# ============================================================================
# VALIDATION
# ============================================================================

clawdie_shell_system_validate() {
  # Verify system configuration completed

  if [ ! -f "$RC_CONF" ]; then
    echo "ERROR: rc.conf not found" >&2
    return 1
  fi

  # Check timezone is set
  if ! grep -q "^timezone=" "$RC_CONF"; then
    echo "ERROR: timezone not set in rc.conf" >&2
    return 1
  fi

  # Check hostname file exists
  if [ ! -f "$HOSTNAME_FILE" ]; then
    echo "ERROR: $HOSTNAME_FILE not created" >&2
    return 1
  fi

  # Check mac_do framework config exists. Empty rules intentionally grant no
  # credential transitions.
  if ! grep -q '^mac_do_load="YES"' "$LOADER_CONF" 2>/dev/null; then
    echo "ERROR: mac_do_load not set in $LOADER_CONF" >&2
    return 1
  fi
  if ! grep -q '^security.mac.do.rules=' "$SYSCTL_CONF" 2>/dev/null; then
    echo "ERROR: security.mac.do.rules not set in $SYSCTL_CONF" >&2
    return 1
  fi

  if ! grep -q '^net.link.tap.up_on_open=1$' "$SYSCTL_CONF" 2>/dev/null; then
    echo "ERROR: net.link.tap.up_on_open not set in $SYSCTL_CONF" >&2
    return 1
  fi

  # Check environment profile exists
  if [ ! -f "$PROFILE_DIR/clawdie.sh" ]; then
    echo "ERROR: $PROFILE_DIR/clawdie.sh not created" >&2
    return 1
  fi

  if ! grep -q '^linux_enable=YES$' "$RC_CONF" 2>/dev/null; then
    echo "ERROR: linux_enable not set in $RC_CONF" >&2
    return 1
  fi

  if ! grep -q '^vm_enable=YES$' "$RC_CONF" 2>/dev/null; then
    echo "ERROR: vm_enable not set in $RC_CONF" >&2
    return 1
  fi

  if ! grep -q '^kld_list=' "$RC_CONF" 2>/dev/null; then
    echo "ERROR: kld_list not set in $RC_CONF" >&2
    return 1
  fi

  log_msg "[system] Validation passed"
  return 0
}

# ============================================================================
# UTILITY: Logging
# ============================================================================

log_msg() {
  local msg="$1"
  echo "$msg" >> "$LOG_FILE" 2>/dev/null || true
}

# ============================================================================
# Export for use by firstboot.sh
# ============================================================================

case "${0##*/}" in
  clawdie-shell-system.sh)
    # Direct execution (for testing)
    clawdie_shell_system_config
    clawdie_shell_system_validate
    ;;
  *)
    # Sourced from another script — functions available
    ;;
esac
