#!/bin/bash
#
# Nexus Memory - Ingest Notify Daemon
# Background daemon that polls pending ingestion jobs and updates state files.
# Started by auto-ingest.sh when files are queued for ingestion.
#
# Features:
#   - Polls job status every 10 seconds
#   - Updates pending_jobs.json with current status
#   - Moves completed jobs to recent_completions.json
#   - Self-terminates when no pending jobs remain
#   - Prevents duplicate daemon instances
#
# Environment Variables:
#   NEXUS_API_KEY              - API key for authentication (REQUIRED)
#   NEXUS_API_URL              - API endpoint (default: https://api.adverant.ai)
#   NEXUS_INGEST_POLL_INTERVAL - Poll interval in seconds (default: 10)
#   NEXUS_INGEST_VERBOSE       - Enable debug output (default: 0)
#

set -o pipefail

# Configuration
NEXUS_API_KEY="${NEXUS_API_KEY:-}"
NEXUS_API_URL="${NEXUS_API_URL:-https://api.adverant.ai}"
POLL_INTERVAL="${NEXUS_INGEST_POLL_INTERVAL:-10}"
VERBOSE="${NEXUS_INGEST_VERBOSE:-${NEXUS_VERBOSE:-0}}"

# State files
STATE_DIR="${HOME}/.claude/session-env/auto-ingest"
INGESTED_FILES="${STATE_DIR}/ingested_files.json"
PENDING_JOBS="${STATE_DIR}/pending_jobs.json"
RECENT_COMPLETIONS="${STATE_DIR}/recent_completions.json"
PID_FILE="${STATE_DIR}/notifier.pid"

# Logging functions
log() {
  if [[ "$VERBOSE" == "1" ]]; then
    echo "[ingest-notify] $1" >&2
  fi
}

log_error() {
  echo "[ingest-notify] ERROR: $1" >&2
}

# Check if another instance is running
check_existing_instance() {
  if [[ -f "$PID_FILE" ]]; then
    local existing_pid=$(cat "$PID_FILE" 2>/dev/null)
    if [[ -n "$existing_pid" ]] && kill -0 "$existing_pid" 2>/dev/null; then
      log "Another notifier instance is running (PID: $existing_pid)"
      exit 0
    fi
    # Stale PID file, remove it
    rm -f "$PID_FILE"
  fi
}

# Initialize state files
init_state() {
  mkdir -p "$STATE_DIR"
  [[ -f "$INGESTED_FILES" ]] || echo "{}" > "$INGESTED_FILES"
  [[ -f "$PENDING_JOBS" ]] || echo "[]" > "$PENDING_JOBS"
  [[ -f "$RECENT_COMPLETIONS" ]] || echo "[]" > "$RECENT_COMPLETIONS"
}

# Get file modification time (cross-platform)
get_mtime() {
  local file_path="$1"
  if [[ "$(uname)" == "Darwin" ]]; then
    stat -f %m "$file_path" 2>/dev/null || echo "0"
  else
    stat -c %Y "$file_path" 2>/dev/null || echo "0"
  fi
}

# Update ingested files index
update_ingested_file() {
  local file_path="$1"
  local job_id="$2"
  local status="$3"
  local entity_count="${4:-0}"

  local mtime=$(get_mtime "$file_path")
  local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

  # Atomic update using temp file
  jq --arg p "$file_path" \
     --arg j "$job_id" \
     --arg s "$status" \
     --arg m "$mtime" \
     --arg t "$now" \
     --argjson e "$entity_count" \
     '.[$p] = {
       jobId: $j,
       status: $s,
       mtime: ($m | tonumber),
       ingestedAt: $t,
       entityCount: $e
     }' "$INGESTED_FILES" > "${INGESTED_FILES}.tmp" 2>/dev/null \
    && mv "${INGESTED_FILES}.tmp" "$INGESTED_FILES"
}

# Add to recent completions
add_completion() {
  local file_path="$1"
  local job_id="$2"
  local entity_count="${3:-0}"
  local status="${4:-completed}"

  local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

  # Read existing completions
  local existing=$(cat "$RECENT_COMPLETIONS" 2>/dev/null || echo "[]")

  # Append new completion
  echo "$existing" | jq --arg p "$file_path" \
                        --arg j "$job_id" \
                        --arg t "$now" \
                        --arg s "$status" \
                        --argjson e "$entity_count" \
                        '. + [{
                          path: $p,
                          jobId: $j,
                          completedAt: $t,
                          status: $s,
                          entityCount: $e
                        }]' > "${RECENT_COMPLETIONS}.tmp" 2>/dev/null \
    && mv "${RECENT_COMPLETIONS}.tmp" "$RECENT_COMPLETIONS"
}

# Check status of a single job
check_job_status() {
  local job_id="$1"

  local response=$(curl -s "$NEXUS_API_URL/fileprocess/api/jobs/$job_id" \
    -H "Authorization: Bearer $NEXUS_API_KEY" \
    --max-time 10 2>/dev/null)

  if [[ -z "$response" ]]; then
    echo '{"status": "unknown", "error": "No response from API"}'
    return 1
  fi

  # Extract status from response (handles nested .job.status structure)
  local status=$(echo "$response" | jq -r '.job.status // .status // "unknown"' 2>/dev/null)
  local error=$(echo "$response" | jq -r '.job.errorMessage // .error // null' 2>/dev/null)
  local entity_count=$(echo "$response" | jq -r '.job.metadata.entities // [] | length' 2>/dev/null || echo "0")

  jq -n --arg s "$status" --arg e "$error" --argjson c "$entity_count" \
    '{status: $s, error: $e, entityCount: $c}'
}

# Poll all pending jobs
poll_pending_jobs() {
  local pending=$(cat "$PENDING_JOBS" 2>/dev/null || echo "[]")
  local pending_count=$(echo "$pending" | jq 'length' 2>/dev/null || echo "0")

  if [[ "$pending_count" == "0" ]]; then
    log "No pending jobs"
    return 0
  fi

  log "Polling $pending_count pending jobs..."

  # Build new pending list (jobs still in progress)
  local still_pending="[]"

  # Process each pending job
  echo "$pending" | jq -c '.[]' 2>/dev/null | while read -r job; do
    local job_id=$(echo "$job" | jq -r '.jobId')
    local file_path=$(echo "$job" | jq -r '.path')
    local file_name=$(basename "$file_path")

    log "Checking job $job_id for $file_name"

    # Get job status
    local status_json=$(check_job_status "$job_id")
    local status=$(echo "$status_json" | jq -r '.status')
    local entity_count=$(echo "$status_json" | jq -r '.entityCount // 0')

    case "$status" in
      "completed"|"finished"|"success")
        log "Job completed: $file_name ($entity_count entities)"
        update_ingested_file "$file_path" "$job_id" "completed" "$entity_count"
        add_completion "$file_path" "$job_id" "$entity_count" "completed"
        ;;

      "failed"|"error"|"cancelled")
        local error=$(echo "$status_json" | jq -r '.error // "Unknown error"')
        log "Job failed: $file_name - $error"
        update_ingested_file "$file_path" "$job_id" "failed" "0"
        add_completion "$file_path" "$job_id" "0" "failed"
        ;;

      "queued"|"pending"|"processing"|"active"|"triaging"|"routing")
        log "Job still processing: $file_name ($status)"
        # Keep in pending list - will be rebuilt after loop
        ;;

      *)
        log "Unknown status for $file_name: $status"
        # Keep in pending list
        ;;
    esac
  done

  # Rebuild pending jobs list (only keep non-completed jobs)
  local updated_pending=$(cat "$PENDING_JOBS" 2>/dev/null || echo "[]")
  local new_pending="[]"

  echo "$updated_pending" | jq -c '.[]' 2>/dev/null | while read -r job; do
    local job_id=$(echo "$job" | jq -r '.jobId')
    local file_path=$(echo "$job" | jq -r '.path')

    # Check if this job is completed in ingested_files
    local ingested_status=$(jq -r --arg p "$file_path" '.[$p].status // "pending"' "$INGESTED_FILES" 2>/dev/null)

    if [[ "$ingested_status" != "completed" ]] && [[ "$ingested_status" != "failed" ]]; then
      # Still pending, keep it
      new_pending=$(echo "$new_pending" | jq --argjson job "$job" '. + [$job]')
    fi
  done

  # Calculate remaining pending jobs
  local final_pending=$(cat "$PENDING_JOBS" 2>/dev/null || echo "[]")
  local remaining="[]"

  while IFS= read -r job; do
    if [[ -n "$job" ]]; then
      local job_id=$(echo "$job" | jq -r '.jobId')
      local file_path=$(echo "$job" | jq -r '.path')

      # Check completion status
      local ingested_status=$(jq -r --arg p "$file_path" '.[$p].status // "pending"' "$INGESTED_FILES" 2>/dev/null)

      if [[ "$ingested_status" != "completed" ]] && [[ "$ingested_status" != "failed" ]]; then
        remaining=$(echo "$remaining" | jq --argjson j "$job" '. + [$j]')
      fi
    fi
  done < <(echo "$final_pending" | jq -c '.[]' 2>/dev/null)

  echo "$remaining" > "$PENDING_JOBS"

  local remaining_count=$(echo "$remaining" | jq 'length' 2>/dev/null || echo "0")
  log "Remaining pending jobs: $remaining_count"

  return "$remaining_count"
}

# Cleanup on exit
cleanup() {
  rm -f "$PID_FILE"
  log "Notifier daemon stopped"
}

# Main daemon loop
run_daemon() {
  log "Starting notifier daemon (PID: $$)"
  echo $$ > "$PID_FILE"

  trap cleanup EXIT

  local max_iterations=360  # 1 hour max (360 * 10s = 3600s)
  local iteration=0

  while [[ $iteration -lt $max_iterations ]]; do
    poll_pending_jobs
    local remaining=$?

    # Exit if no more pending jobs
    local pending_count=$(jq 'length' "$PENDING_JOBS" 2>/dev/null || echo "0")
    if [[ "$pending_count" == "0" ]]; then
      log "All jobs completed, exiting daemon"
      break
    fi

    # Wait before next poll
    sleep "$POLL_INTERVAL"
    ((iteration++))
  done

  log "Daemon loop ended after $iteration iterations"
}

# Main entry point
main() {
  # Skip if no API key
  if [[ -z "$NEXUS_API_KEY" ]]; then
    log "NEXUS_API_KEY not set, exiting"
    exit 0
  fi

  # Check dependencies
  if ! command -v jq &> /dev/null; then
    log_error "jq not installed"
    exit 1
  fi

  if ! command -v curl &> /dev/null; then
    log_error "curl not installed"
    exit 1
  fi

  # Check for existing instance
  check_existing_instance

  # Initialize state
  init_state

  # Run daemon
  run_daemon
}

main
