#!/bin/bash

# SessionStart Hook
# Triggered when a Claude Code session starts or resumes
# Initializes sync session and subscribes to mobile events

# Source common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"

log "INFO" "SessionStart hook triggered"

# Read JSON input from stdin first so we can get the session ID
INPUT=$(read_json_input)
log "DEBUG" "Input: $INPUT"

# Extract session ID from input
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
if [ -z "$SESSION_ID" ]; then
  log "ERROR" "No session_id in input"
  exit 1
fi

log "INFO" "Session ID: $SESSION_ID"

# Find the plugin root directory (parent of hooks directory)
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# Mapping file tracks which MCP server is running for this Claude Code instance (tmux session)
TMUX_SESSION="${CODEVIBE_TMUX_SESSION:-}"
if [ -n "$TMUX_SESSION" ]; then
  MAPPING_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-instance-${TMUX_SESSION}.json"
else
  # Fallback: use session-specific files if no tmux session
  MAPPING_FILE=""
fi

# ─── Orphan daemon sweep ───────────────────────────────────────────────────
#
# When Claude Code terminates abnormally (crash, force-kill, window closed
# without /exit, network drop), the SessionEnd hook doesn't fire and the
# background daemon is never sent SIGTERM. Because the daemon is a PPID=1
# process (detached from the hook that launched it), nothing in the OS
# cleans it up — it keeps holding its AppSync subscription, sending
# heartbeats, and showing the session as "desktop connected" on iOS forever.
#
# This sweep runs on every SessionStart and cleans up orphan daemons whose
# tmux session no longer exists. Daemons belonging to live tmux sessions
# (including our own) are left alone.
#
# Scope: the sweep only considers daemons that have an instance mapping file
# (i.e. were launched under a tmux session). Daemons launched without tmux
# have no mapping and cannot be safely identified as orphans, so we leave
# them alone.
#
# Safety guards:
#
# (A) PID reuse: before killing a PID, verify via `ps -o command=` that the
#     process is actually a node + codevibe-claude + server.js invocation.
#     PIDs are recyclable (macOS cycles through ~32K), so a stale mapping
#     from weeks ago could end up pointing at an unrelated user process.
#     Without identity verification, we'd kill random things.
#
# (B) tmux unavailable: if the `tmux` binary is missing or fails for a reason
#     other than "session doesn't exist", we conservatively treat the session
#     as alive and leave the daemon alone. This prevents a degraded-tmux
#     environment from causing a sweep-induced mass kill.

# Check whether a PID is still identifiable as a codevibe-claude daemon.
# Returns 0 if yes, non-zero otherwise.
is_codevibe_daemon() {
  local pid="$1"
  local cmd
  cmd=$(ps -o command= -p "$pid" 2>/dev/null)
  # Pattern: "node" + "codevibe-claude" + "server.js" somewhere in the command line.
  # Matches both the cache path (...codevibe-marketplace/codevibe-claude/X.Y.Z/dist/server.js)
  # and the dev path (...codevibe-claude-plugin/dist/server.js).
  case "$cmd" in
    *node*codevibe-claude*server.js*) return 0 ;;
    *) return 1 ;;
  esac
}

# Return 0 if the tmux session is alive OR we can't tell.
# Return non-zero ONLY when we can prove the session is gone: tmux binary exists,
# tmux server is reachable, and the server says this specific session doesn't exist.
# Every other failure mode (no binary, server down, socket error, permission issue)
# returns 0 — conservative, we'd rather leak a daemon than kill the wrong thing.
tmux_session_alive() {
  local tmux_name="$1"
  if ! command -v tmux >/dev/null 2>&1; then
    return 0  # no tmux binary → can't verify → assume alive
  fi
  # Verify the tmux server itself is reachable before trusting has-session.
  # `tmux ls` exits 0 when the server is running and has at least one session.
  # If it fails (server not running, socket error, no sessions at all), we can't
  # reliably distinguish "this session is gone" from "tmux is broken." Conservative
  # choice: assume the session might still be alive (or recoverable when the server
  # comes back).
  if ! tmux ls >/dev/null 2>&1; then
    return 0  # server unreachable or empty → can't verify → assume alive
  fi
  # Server is confirmed reachable and has sessions. has-session non-zero now
  # reliably means "this specific session does not exist."
  tmux has-session -t "$tmux_name" 2>/dev/null
}

sweep_orphan_daemons() {
  local swept=0
  local kept=0
  local cleaned_stale=0

  # Pass 1: walk mapping files, which are the source of truth for (tmux_session, pid) pairs
  shopt -s nullglob
  local map_file
  for map_file in "${CODEVIBE_TMPDIR}"/codevibe-claude-instance-*.json; do
    # Extract tmux session name from filename
    local orphan_tmux
    orphan_tmux=$(basename "$map_file" .json | sed 's/^codevibe-claude-instance-//')

    # Skip our own tmux session — we handle it in the existing reuse path below
    if [ -n "$TMUX_SESSION" ] && [ "$orphan_tmux" = "$TMUX_SESSION" ]; then
      continue
    fi

    local orphan_pid
    orphan_pid=$(jq -r '.pid // empty' "$map_file" 2>/dev/null)

    # Empty or missing PID in mapping — clean up the mapping file
    if [ -z "$orphan_pid" ]; then
      log "INFO" "Orphan sweep: removing mapping with no PID: $map_file"
      rm -f "$map_file"
      cleaned_stale=$((cleaned_stale + 1))
      continue
    fi

    # PID is no longer running — clean up the stale mapping.
    # NOTE: do NOT touch the pidfile or portfile by session UUID here. If /resume
    # or a UUID collision caused a stale mapping to reference the same session
    # UUID as a currently-live pidfile, we'd delete the live one. Pass 2 below
    # handles pidfile cleanup by verifying each pidfile's content independently.
    if ! ps -p "$orphan_pid" > /dev/null 2>&1; then
      log "INFO" "Orphan sweep: removing stale mapping for dead PID $orphan_pid (tmux $orphan_tmux)"
      rm -f "$map_file"
      cleaned_stale=$((cleaned_stale + 1))
      continue
    fi

    # PID is alive. Is its tmux session still alive (or unknowable)?
    if tmux_session_alive "$orphan_tmux"; then
      # Live other session — or tmux is unavailable and we can't tell. Either
      # way, the conservative choice is to leave the daemon alone.
      log "DEBUG" "Orphan sweep: tmux $orphan_tmux is alive (or tmux unavailable), leaving daemon $orphan_pid"
      kept=$((kept + 1))
      continue
    fi

    # tmux is gone but the PID is alive — looks like an orphan. But before
    # killing, verify the PID actually belongs to a codevibe-claude daemon.
    # If PID reuse has handed this PID to an unrelated process, we must not
    # touch it. Just clean up the stale mapping and move on.
    if ! is_codevibe_daemon "$orphan_pid"; then
      log "INFO" "Orphan sweep: PID $orphan_pid is alive but is not a CodeVibe daemon (likely PID reused), removing stale mapping only"
      rm -f "$map_file"
      cleaned_stale=$((cleaned_stale + 1))
      continue
    fi

    # Verified: this is our daemon and its tmux is gone. Kill it.
    log "WARN" "Orphan sweep: killing orphan daemon PID $orphan_pid (tmux $orphan_tmux is gone)"

    # SIGTERM first — lets the daemon run its graceful shutdown (mark session INACTIVE, etc.)
    kill "$orphan_pid" 2>/dev/null || true

    # Give it a moment to exit cleanly, then SIGKILL if it's still alive
    sleep 0.5
    if ps -p "$orphan_pid" > /dev/null 2>&1; then
      log "WARN" "Orphan sweep: daemon $orphan_pid did not exit from SIGTERM, sending SIGKILL"
      kill -9 "$orphan_pid" 2>/dev/null || true
    fi

    # Remove the mapping file. Do NOT touch pidfile/portfile by session UUID —
    # Pass 2 below will pick up the now-dead PID and clean the pidfile (and its
    # matching portfile) safely, based on PID content, not mapping contents.
    rm -f "$map_file"
    swept=$((swept + 1))
  done

  # Pass 2: defensive cleanup of stale pidfiles that have no mapping and are dead
  # (e.g. daemons that died before writing a mapping file, or legacy non-tmux launches)
  local pid_file
  for pid_file in "${CODEVIBE_TMPDIR}"/codevibe-claude-*.pid; do
    # Skip the instance-mapping files' PID files (they have 'instance' in the name and are .json not .pid,
    # but be explicit in case the glob matches something unexpected)
    local base
    base=$(basename "$pid_file")
    case "$base" in
      codevibe-claude-instance-*) continue ;;
    esac

    local pid
    pid=$(cat "$pid_file" 2>/dev/null)
    if [ -z "$pid" ] || ! ps -p "$pid" > /dev/null 2>&1; then
      log "INFO" "Orphan sweep: removing stale pidfile $pid_file (PID ${pid:-empty} not alive)"
      rm -f "$pid_file"
      # Also clean up the corresponding portfile
      local port_file="${pid_file%.pid}.port"
      rm -f "$port_file"
      cleaned_stale=$((cleaned_stale + 1))
    fi
  done
  shopt -u nullglob

  if [ $swept -gt 0 ] || [ $cleaned_stale -gt 0 ]; then
    log "INFO" "Orphan sweep: killed $swept orphan daemon(s), cleaned $cleaned_stale stale file(s), kept $kept live session(s)"
  else
    log "DEBUG" "Orphan sweep: nothing to clean (kept $kept live session(s))"
  fi
}

# Run the sweep before touching the mapping file for our own session
sweep_orphan_daemons || log "WARN" "Orphan sweep failed (non-fatal, continuing)"

# Check if there's an MCP server running for this Claude Code instance
EXISTING_SERVER_PORT=""
EXISTING_SERVER_PID=""
if [ -n "$MAPPING_FILE" ] && [ -f "$MAPPING_FILE" ]; then
  EXISTING_SERVER_PORT=$(jq -r '.port // empty' "$MAPPING_FILE" 2>/dev/null)
  EXISTING_SERVER_PID=$(jq -r '.pid // empty' "$MAPPING_FILE" 2>/dev/null)
  OLD_SESSION_ID=$(jq -r '.session // empty' "$MAPPING_FILE" 2>/dev/null)

  if [ -n "$EXISTING_SERVER_PID" ] && ps -p "$EXISTING_SERVER_PID" > /dev/null 2>&1; then
    log "INFO" "Found running MCP server for this instance (session: $OLD_SESSION_ID, port: $EXISTING_SERVER_PORT, pid: $EXISTING_SERVER_PID)"
  else
    log "INFO" "MCP server from mapping not running, will start new one"
    EXISTING_SERVER_PORT=""
    EXISTING_SERVER_PID=""
    rm -f "$MAPPING_FILE"
  fi
fi

# If existing server found, send event to it and let it handle session transition
if [ -n "$EXISTING_SERVER_PORT" ]; then
  log "INFO" "Using existing MCP server on port $EXISTING_SERVER_PORT for session $SESSION_ID"
  RESPONSE=$(curl -s -X POST "http://localhost:${EXISTING_SERVER_PORT}/event" \
    -H "Content-Type: application/json" \
    -d "$INPUT" 2>&1)
  CURL_EXIT=$?
  if [ $CURL_EXIT -eq 0 ]; then
    log "INFO" "SessionStart event sent to existing server"
    # Update mapping with new session ID
    echo "{\"session\":\"$SESSION_ID\",\"port\":$EXISTING_SERVER_PORT,\"pid\":$EXISTING_SERVER_PID}" > "$MAPPING_FILE"
    log "INFO" "Updated mapping for session $SESSION_ID"
    exit 0
  else
    log "WARN" "Failed to send to existing server (exit: $CURL_EXIT), will start new one"
  fi
fi

# No running server found, start a new one
PID_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-${SESSION_ID}.pid"
PORT_FILE="${CODEVIBE_TMPDIR}/codevibe-claude-${SESSION_ID}.port"

# Start the MCP server with the session ID
# Pass CODEVIBE_TMUX_SESSION so server can use tmux send-keys for mobile input
# Change to plugin directory so .env file can be found by dotenvx
log "INFO" "Starting MCP server for session $SESSION_ID..."
log "DEBUG" "CODEVIBE_TMUX_SESSION=${CODEVIBE_TMUX_SESSION:-not set}"
log "DEBUG" "ENVIRONMENT=${ENVIRONMENT:-development}"
log "DEBUG" "Starting server from directory: $PLUGIN_ROOT"
# Use exec to replace subshell with node process so $! captures correct PID
# Pass through ENVIRONMENT variable for correct Cognito configuration
(cd "$PLUGIN_ROOT" && exec env CODEVIBE_TMUX_SESSION="${CODEVIBE_TMUX_SESSION}" ENVIRONMENT="${ENVIRONMENT:-development}" node "$PLUGIN_ROOT/dist/server.js" "$SESSION_ID") >> "${CODEVIBE_TMPDIR}/codevibe-claude-mcp.log" 2>&1 &
SERVER_PID=$!
echo "$SERVER_PID" > "$PID_FILE"

# Wait for server to start and port file to be created
MAX_WAIT=10
WAITED=0
while [ ! -f "$PORT_FILE" ] && [ $WAITED -lt $MAX_WAIT ]; do
  sleep 0.5
  WAITED=$((WAITED + 1))
  log "DEBUG" "Waiting for port file... ($WAITED)"
done

if [ -f "$PORT_FILE" ]; then
  PORT=$(cat "$PORT_FILE")
  log "INFO" "MCP server started (PID: $SERVER_PID, Port: $PORT)"
  # Create mapping file for this Claude Code instance
  if [ -n "$MAPPING_FILE" ]; then
    echo "{\"session\":\"$SESSION_ID\",\"port\":$PORT,\"pid\":$SERVER_PID}" > "$MAPPING_FILE"
    log "INFO" "Created mapping file: $MAPPING_FILE"
  fi
else
  # Check if server process died (e.g., auth failure causes process.exit(1))
  if ! ps -p "$SERVER_PID" > /dev/null 2>&1; then
    log "ERROR" "MCP server died on startup (likely auth failure). Run 'codevibe-claude login' first."
    echo ""
    echo "⚠️  CodeVibe: Not authenticated. Mobile sync is disabled."
    echo "   Run 'codevibe-claude login' to sign in and enable mobile sync."
    echo ""
  else
    log "WARN" "Port file not created after ${MAX_WAIT}s, server may still be starting"
  fi
fi

# Send to MCP server
send_to_mcp "event" "$INPUT" "$SESSION_ID"

EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
  log "INFO" "SessionStart event sent successfully"
else
  log "WARN" "Failed to send SessionStart event (exit code: $EXIT_CODE)"
  log "WARN" "Run 'codevibe-claude login' to enable mobile sync"
fi

exit 0
