#!/bin/zsh
#
# Daily Config Audit
# Checks for unauthorized changes to critical OpenClaw configuration files
# Runs once daily via cron (recommended: 6 AM)
#

STATE_DIR="/Users/mattbruce/.openclaw/workspace/scripts/security-monitors/state"
LOG_FILE="/Users/mattbruce/.openclaw/workspace/scripts/security-monitors/logs/config-audit.log"
BASELINE_DIR="$STATE_DIR/baselines"
AUDIT_REPORT="$STATE_DIR/daily-audit-report.txt"

# Create directories
mkdir -p "$(dirname $LOG_FILE)" "$STATE_DIR" "$BASELINE_DIR"

# Timestamp helper
timestamp() {
  date '+%Y-%m-%d %H:%M:%S %Z'
}

# Log to file
log() {
  echo "[$(timestamp)] $1" >> "$LOG_FILE"
}

# Send alert to queue
send_alert() {
  local level="$1"
  local message="$2"
  echo "$(timestamp) | $level | CONFIG | $message" >> "$STATE_DIR/alerts.queue"
}

# Define critical files to monitor
get_critical_files() {
  cat << 'EOF'
/Users/mattbruce/.openclaw/openclaw.json
/Users/mattbruce/.openclaw/workspace/AGENTS.md
/Users/mattbruce/.openclaw/workspace/TOOLS.md
/Users/mattbruce/.openclaw/workspace/BRAIN.md
/Users/mattbruce/.openclaw/workspace/SOUL.md
/Users/mattbruce/.openclaw/workspace/HEARTBEAT.md
/Users/mattbruce/.openclaw/workspace/.openclaw/workspace-state.json
/etc/ssh/sshd_config
/etc/hosts
~/.ssh/authorized_keys
~/.zshrc
~/.bash_profile
EOF
}

# Calculate file hash (MD5 for speed, or SHA256 for security)
calculate_hash() {
  local file="$1"
  if [[ -f "$file" ]]; then
    md5 -q "$file" 2>/dev/null || md5sum "$file" 2>/dev/null | awk '{print $1}' || echo "ERROR"
  else
    echo "MISSING"
  fi
}

# Get file metadata
get_metadata() {
  local file="$1"
  if [[ -f "$file" ]]; then
    stat -f "%Sm|%Su|%Sg|%A" -t "%Y-%m-%d %H:%M:%S" "$file" 2>/dev/null || stat -c "%y|%U|%G|%a" "$file" 2>/dev/null
  else
    echo "MISSING"
  fi
}

# Build baseline for a file
build_baseline() {
  local file="$1"
  local baseline_file="$BASELINE_DIR/$(echo "$file" | tr '/' '_').baseline"
  
  local hash
  hash=$(calculate_hash "$file")
  local metadata
  metadata=$(get_metadata "$file")
  
  echo "${hash}|${metadata}|$(timestamp)" > "$baseline_file"
  echo "Baseline created for $file"
}

# Check file against baseline
check_file() {
  local file="$1"
  local baseline_file="$BASELINE_DIR/$(echo "$file" | tr '/' '_').baseline"
  
  if [[ ! -f "$baseline_file" ]]; then
    # No baseline exists, create one
    build_baseline "$file"
    echo "NEW|${file}|No baseline existed"
    return
  fi
  
  if [[ ! -f "$file" ]]; then
    # File was deleted!
    echo "DELETED|${file}|File no longer exists"
    return
  fi
  
  # Read baseline
  local baseline_data
  baseline_data=$(cat "$baseline_file")
  local baseline_hash=$(echo "$baseline_data" | cut -d'|' -f1)
  local baseline_meta=$(echo "$baseline_data" | cut -d'|' -f2)
  
  # Get current state
  local current_hash
  current_hash=$(calculate_hash "$file")
  local current_meta
  current_meta=$(get_metadata "$file")
  
  # Compare
  if [[ "$current_hash" != "$baseline_hash" ]]; then
    echo "MODIFIED|${file}|Hash changed: $baseline_hash -> $current_hash"
  elif [[ "$current_meta" != "$baseline_meta" ]]; then
    echo "METACHANGE|${file}|Metadata changed: $baseline_meta -> $current_meta"
  else
    echo "UNCHANGED|${file}|"
  fi
}

# Check git repositories for uncommitted changes
check_git_repos() {
  local changes_found=""
  
  # Check ~/.openclaw if it's a git repo
  if [[ -d "$HOME/.openclaw/.git" ]]; then
    local git_status
    git_status=$(cd "$HOME/.openclaw" && git status --porcelain 2>/dev/null)
    if [[ -n "$git_status" ]]; then
      changes_found="${changes_found}OPENCLAW_CONFIG:\n${git_status}\n\n"
    fi
  fi
  
  # Check workspace
  if [[ -d "$HOME/.openclaw/workspace/.git" ]]; then
    local git_status
    git_status=$(cd "$HOME/.openclaw/workspace" && git status --porcelain 2>/dev/null)
    if [[ -n "$git_status" ]]; then
      changes_found="${changes_found}WORKSPACE:\n${git_status}\n\n"
    fi
  fi
  
  echo "$changes_found"
}

# Main audit logic
main() {
  local mode="${1:-check}"  # check, init, or report
  
  log "=== Starting Config Audit (mode: $mode) ==="
  
  if [[ "$mode" == "init" ]]; then
    # Initialize all baselines
    log "Initializing baselines for all critical files..."
    get_critical_files | while read -r file; do
      [[ -z "$file" ]] && continue
      # Expand ~ to $HOME
      file="${file/#\~/$HOME}"
      if [[ -f "$file" ]]; then
        build_baseline "$file"
        log "Baseline created: $file"
      else
        log "File not found (skipped): $file"
      fi
    done
    log "Baseline initialization complete"
    return
  fi
  
  # Perform the audit
  local changes=()
  local modifications=0
  local deletions=0
  local new_files=0
  local meta_changes=0
  
  # Clear previous report
  echo "OpenClaw Config Audit Report" > "$AUDIT_REPORT"
  echo "Generated: $(timestamp)" >> "$AUDIT_REPORT"
  echo "========================================" >> "$AUDIT_REPORT"
  echo "" >> "$AUDIT_REPORT"
  
  # Check each critical file
  while IFS= read -r file; do
    [[ -z "$file" ]] && continue
    # Expand ~ to $HOME
    file="${file/#\~/$HOME}"
    
    local result
    result=$(check_file "$file")
    local file_status=$(echo "$result" | cut -d'|' -f1)
    local filepath=$(echo "$result" | cut -d'|' -f2)
    local details=$(echo "$result" | cut -d'|' -f3-)
    
    case "$file_status" in
      MODIFIED)
        changes+=("📝 MODIFIED: $filepath")
        modifications=$((modifications + 1))
        echo "📝 MODIFIED: $filepath" >> "$AUDIT_REPORT"
        echo "   Details: $details" >> "$AUDIT_REPORT"
        ;;
      DELETED)
        changes+=("🗑️ DELETED: $filepath")
        deletions=$((deletions + 1))
        echo "🗑️ DELETED: $filepath" >> "$AUDIT_REPORT"
        ;;
      NEW)
        changes+=("📄 NEW: $filepath")
        new_files=$((new_files + 1))
        echo "📄 NEW: $filepath" >> "$AUDIT_REPORT"
        ;;
      METACHANGE)
        changes+=("🔧 META: $filepath")
        meta_changes=$((meta_changes + 1))
        echo "🔧 METADATA CHANGED: $filepath" >> "$AUDIT_REPORT"
        echo "   Details: $details" >> "$AUDIT_REPORT"
        ;;
    esac
  done <<< "$(get_critical_files)"
  
  # Check git repos
  local git_changes
  git_changes=$(check_git_repos)
  if [[ -n "$git_changes" ]]; then
    echo "" >> "$AUDIT_REPORT"
    echo "📦 UNCOMMITTED GIT CHANGES:" >> "$AUDIT_REPORT"
    echo "$git_changes" >> "$AUDIT_REPORT"
  fi
  
  # Summary
  local total_changes=$((modifications + deletions + new_files + meta_changes))
  
  echo "" >> "$AUDIT_REPORT"
  echo "========================================" >> "$AUDIT_REPORT"
  echo "Summary:" >> "$AUDIT_REPORT"
  echo "  Modified files: $modifications" >> "$AUDIT_REPORT"
  echo "  Deleted files: $deletions" >> "$AUDIT_REPORT"
  echo "  New files: $new_files" >> "$AUDIT_REPORT"
  echo "  Metadata changes: $meta_changes" >> "$AUDIT_REPORT"
  echo "  Total changes: $total_changes" >> "$AUDIT_REPORT"
  
  # Update baselines for any changes detected (so we don't re-alert)
  if [[ $total_changes -gt 0 ]]; then
    log "Detected $total_changes configuration changes"
    
    # Rebuild baselines for modified files
    while IFS= read -r file; do
      [[ -z "$file" ]] && continue
      file="${file/#\~/$HOME}"
      local result
      result=$(check_file "$file")
      local check_status=$(echo "$result" | cut -d'|' -f1)
      if [[ "$check_status" == "MODIFIED" ]] || [[ "$check_status" == "METACHANGE" ]]; then
        build_baseline "$file"
      fi
    done <<< "$(get_critical_files)"
    
    # Send alert if significant changes detected
    if [[ $total_changes -gt 0 ]] && [[ "$mode" == "check" ]]; then
      local hostname=$(hostname -s)
      local change_list=""
      for change in "${changes[@]}"; do
        change_list="${change_list}${change}\n"
      done
      
      local alert_msg="🔍 **Daily Config Audit Alert** 🔍

**Host:** $hostname
**Time:** $(timestamp)
**Changes Detected:** $total_changes

**Summary:**
• Modified: $modifications
• Deleted: $deletions
• New files: $new_files
• Metadata changes: $meta_changes

**Details:**
$change_list

$(if [[ -n "$git_changes" ]]; then echo "📦 **Uncommitted git changes also detected**"; fi)

_Review these changes to ensure they were authorized._

_Detected by OpenClaw Config Audit_"
      
      send_alert "AUDIT" "$alert_msg"
      log "Audit alert sent with $total_changes changes"
      
      # Log to daily security log
      local daily_log="/Users/mattbruce/.openclaw/workspace/memory/$(date '+%Y-%m-%d')-security.log"
      echo "CONFIG_AUDIT|$(timestamp)|$total_changes changes detected" >> "$daily_log"
    fi
  else
    log "No configuration changes detected"
    echo "" >> "$AUDIT_REPORT"
    echo "✅ No changes detected - all clear!" >> "$AUDIT_REPORT"
  fi
  
  log "=== Config Audit Complete ==="
  
  # If report mode, output the report
  if [[ "$mode" == "report" ]]; then
    cat "$AUDIT_REPORT"
  fi
}

# Run main function
main "$@"
