#!/usr/bin/env bash
# auto-deploy.sh — Pull from Forgejo, sync to working dirs, restart if needed.
# Runs as systemd timer (teleo-auto-deploy.timer) every 2 minutes.
# Exits silently when nothing has changed.
set -euo pipefail

LOCK_FILE="/tmp/teleo-auto-deploy.lock"
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
  logger -t "auto-deploy" "Another deploy is already running. Skipping."
  exit 0
fi

DEPLOY_CHECKOUT="/opt/teleo-eval/workspaces/deploy-infra"
PIPELINE_DIR="/opt/teleo-eval/pipeline"
TELEGRAM_DIR="/opt/teleo-eval/telegram"
DIAGNOSTICS_DIR="/opt/teleo-eval/diagnostics"
AGENT_STATE_DIR="/opt/teleo-eval/ops/agent-state"
LEOCLEAN_BIN_DIR="/home/teleo/.hermes/profiles/leoclean/bin"
LEOCLEAN_SKILLS_DIR="/home/teleo/.hermes/profiles/leoclean/skills"
LEOCLEAN_PLUGINS_DIR="/home/teleo/.hermes/profiles/leoclean/plugins"
LEOCLEAN_CONFIG="/home/teleo/.hermes/profiles/leoclean/config.yaml"
HERMES_AGENT_DIR="/home/teleo/.hermes/hermes-agent"
HERMES_PATCH_DIR="/home/teleo/.hermes/teleo-runtime-patches"
LEOCLEAN_POLICY_PATCH="$HERMES_PATCH_DIR/enforce_leoclean_learning_policy.py"
SYSTEMD_DIR="/etc/systemd/system"
STAMP_FILE="/opt/teleo-eval/.last-deploy-sha"
LOG_TAG="auto-deploy"

log() { logger -t "$LOG_TAG" "$1"; echo "$(date '+%Y-%m-%d %H:%M:%S') $1"; }

DEPLOY_REMOTE="${TELEO_DEPLOY_REMOTE:-}"
if [ -z "$DEPLOY_REMOTE" ]; then
  if git -C "$DEPLOY_CHECKOUT" remote get-url github >/dev/null 2>&1; then
    DEPLOY_REMOTE="github"
  else
    DEPLOY_REMOTE="origin"
  fi
fi

if [ ! -d "$DEPLOY_CHECKOUT/.git" ]; then
  log "ERROR: Deploy checkout not found at $DEPLOY_CHECKOUT. Run setup first."
  exit 1
fi

cd "$DEPLOY_CHECKOUT"
if ! git remote get-url "$DEPLOY_REMOTE" >/dev/null 2>&1; then
  log "ERROR: deploy remote '$DEPLOY_REMOTE' is not configured"
  exit 1
fi
if ! git fetch "$DEPLOY_REMOTE" main --quiet 2>&1; then
  log "ERROR: git fetch failed for $DEPLOY_REMOTE/main"
  exit 1
fi

NEW_SHA=$(git rev-parse "$DEPLOY_REMOTE/main")
OLD_SHA=$(cat "$STAMP_FILE" 2>/dev/null || echo "none")

if [ "$NEW_SHA" = "$OLD_SHA" ]; then
  LEOCLEAN_DRIFT_REPAIRED=false
  # Hermes can be upgraded independently of this repository. Repair an
  # overwritten response-transform hook even when no infrastructure commit
  # changed, and restart only when the patch was actually reapplied.
  if [ -f "$HERMES_PATCH_DIR/apply_response_transform_hook.py" ] && [ -f "$HERMES_AGENT_DIR/run_agent.py" ]; then
    if ! PATCH_RESULT=$(python3 "$HERMES_PATCH_DIR/apply_response_transform_hook.py" "$HERMES_AGENT_DIR/run_agent.py"); then
      log "ERROR: Hermes response-transform drift could not be repaired"
      exit 1
    fi
    if echo "$PATCH_RESULT" | grep -q '"status": "installed_now"'; then
      log "Hermes response-transform drift repaired: $PATCH_RESULT"
      LEOCLEAN_DRIFT_REPAIRED=true
    fi
  fi
  if [ -f "$LEOCLEAN_POLICY_PATCH" ] && [ -f "$LEOCLEAN_CONFIG" ]; then
    if ! POLICY_RESULT=$(python3 "$LEOCLEAN_POLICY_PATCH" "$LEOCLEAN_CONFIG"); then
      log "ERROR: leoclean learning-policy drift could not be repaired"
      exit 1
    fi
    if echo "$POLICY_RESULT" | grep -q '"status": "updated"'; then
      log "Leoclean learning-policy drift repaired: $POLICY_RESULT"
      LEOCLEAN_DRIFT_REPAIRED=true
    fi
  fi
  if $LEOCLEAN_DRIFT_REPAIRED && systemctl is-active --quiet leoclean-gateway.service; then
    sudo systemctl restart leoclean-gateway
    sleep 10
    if ! systemctl is-active --quiet leoclean-gateway.service; then
      log "ERROR: leoclean-gateway failed after runtime-policy drift repair"
      exit 1
    fi
  fi
  exit 0
fi

log "New commits: ${OLD_SHA:0:8} -> ${NEW_SHA:0:8}"

if ! git checkout main --quiet 2>&1; then
  log "ERROR: git checkout main failed — dirty tree or corrupted index"
  exit 1
fi
if ! git merge --ff-only "$DEPLOY_REMOTE/main" --quiet 2>&1; then
  log "ERROR: git merge --ff-only $DEPLOY_REMOTE/main failed. Manual intervention needed."
  exit 1
fi

# A running shell keeps the old script body after git updates this file. Re-exec
# once from the merged checkout so newly added sync/restart paths take effect in
# the same deploy instead of writing a false-success stamp.
if [ "${TELEO_AUTO_DEPLOY_REEXECED:-0}" != "1" ] \
  && [ "$OLD_SHA" != "none" ] \
  && git diff --name-only "$OLD_SHA" "$NEW_SHA" -- deploy/auto-deploy.sh deploy/leoclean-gateway-restart-required.sh \
    | grep -q .; then
  log "Deploy logic changed; re-executing merged deploy script"
  export TELEO_AUTO_DEPLOY_REEXECED=1
  exec bash "$DEPLOY_CHECKOUT/deploy/auto-deploy.sh"
fi

# Syntax check all Python files before copying
ERRORS=0
for f in lib/*.py *.py diagnostics/*.py telegram/*.py tests/*.py hermes-agent/leoclean-bin/*.py hermes-agent/leoclean-plugins/vps/*/*.py hermes-agent/patches/*.py scripts/compile_kb_source_packet.py scripts/kb_proposal_normalize.py scripts/prepare_kb_source_manifest.py scripts/leo_behavior_manifest.py scripts/leo_turn_execution_manifest.py; do
  [ -f "$f" ] || continue
  if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>&1; then
    log "SYNTAX ERROR: $f"
    ERRORS=$((ERRORS + 1))
  fi
done
if [ "$ERRORS" -gt 0 ]; then
  log "ERROR: $ERRORS syntax errors. Deploy aborted. Fix and push again."
  exit 1
fi
log "Syntax check passed"

# Sync to working directories
RSYNC_OPTS=(-az --exclude __pycache__ --exclude '*.pyc' --exclude '*.bak*')

rsync "${RSYNC_OPTS[@]}" lib/ "$PIPELINE_DIR/lib/"

for f in teleo-pipeline.py reweave.py fetch_coins.py pipeline-health-check.py; do
  [ -f "$f" ] && rsync "${RSYNC_OPTS[@]}" "$f" "$PIPELINE_DIR/$f"
done

rsync "${RSYNC_OPTS[@]}" telegram/ "$PIPELINE_DIR/telegram/"
rsync "${RSYNC_OPTS[@]}" telegram/ "$TELEGRAM_DIR/"
rsync "${RSYNC_OPTS[@]}" diagnostics/ "$DIAGNOSTICS_DIR/"
rsync "${RSYNC_OPTS[@]}" agent-state/ "$AGENT_STATE_DIR/"
rsync "${RSYNC_OPTS[@]}" tests/ "$PIPELINE_DIR/tests/"
if [ -d hermes-agent/leoclean-bin ]; then
  mkdir -p "$LEOCLEAN_BIN_DIR"
  rsync "${RSYNC_OPTS[@]}" hermes-agent/leoclean-bin/ "$LEOCLEAN_BIN_DIR/"
fi
if [ -d hermes-agent/leoclean-skills/vps ]; then
  mkdir -p "$LEOCLEAN_SKILLS_DIR"
  rsync "${RSYNC_OPTS[@]}" hermes-agent/leoclean-skills/vps/ "$LEOCLEAN_SKILLS_DIR/"
fi
if [ -d hermes-agent/leoclean-plugins/vps ]; then
  mkdir -p "$LEOCLEAN_PLUGINS_DIR"
  rsync "${RSYNC_OPTS[@]}" hermes-agent/leoclean-plugins/vps/ "$LEOCLEAN_PLUGINS_DIR/"
fi
if [ -d hermes-agent/patches ]; then
  mkdir -p "$HERMES_PATCH_DIR"
  rsync "${RSYNC_OPTS[@]}" hermes-agent/patches/ "$HERMES_PATCH_DIR/"
fi
[ -f research/research-session.sh ] && rsync "${RSYNC_OPTS[@]}" research/research-session.sh /opt/teleo-eval/research-session.sh

if [ -f "$HERMES_PATCH_DIR/apply_response_transform_hook.py" ]; then
  PATCH_RESULT=$(python3 "$HERMES_PATCH_DIR/apply_response_transform_hook.py" "$HERMES_AGENT_DIR/run_agent.py")
  log "Hermes response transform: $PATCH_RESULT"
fi
LEOCLEAN_POLICY_CHANGED=false
if [ -f "$LEOCLEAN_POLICY_PATCH" ] && [ -f "$LEOCLEAN_CONFIG" ]; then
  POLICY_RESULT=$(python3 "$LEOCLEAN_POLICY_PATCH" "$LEOCLEAN_CONFIG")
  log "Leoclean learning policy: $POLICY_RESULT"
  if echo "$POLICY_RESULT" | grep -q '"status": "updated"'; then
    LEOCLEAN_POLICY_CHANGED=true
  fi
fi

# Safety net: ensure synced .sh files are executable after rsync.
# Keep this bounded to deploy-owned paths: /opt/teleo-eval also contains
# backups and generated state that may be unreadable by the deploy user.
for dir in "$PIPELINE_DIR" "$TELEGRAM_DIR" "$DIAGNOSTICS_DIR" "$AGENT_STATE_DIR" "$LEOCLEAN_BIN_DIR" "$LEOCLEAN_SKILLS_DIR" "$LEOCLEAN_PLUGINS_DIR" "$HERMES_PATCH_DIR"; do
  [ -d "$dir" ] || continue
  find "$dir" -maxdepth 3 -name '*.sh' -not -perm -u+x -exec chmod +x {} +
done
[ -f /opt/teleo-eval/research-session.sh ] && chmod u+x /opt/teleo-eval/research-session.sh

log "Files synced"

if [ "$OLD_SHA" = "none" ] || git diff --name-only "$OLD_SHA" "$NEW_SHA" -- systemd/ 2>/dev/null | grep -q .; then
  SYSTEMD_CHANGED=false
  for unit in systemd/*.service systemd/*.timer; do
    [ -f "$unit" ] || continue
    installed_unit="$SYSTEMD_DIR/$(basename "$unit")"
    if [ -f "$installed_unit" ] && cmp -s "$unit" "$installed_unit"; then
      continue
    fi
    SYSTEMD_CHANGED=true
    log "Installing changed systemd unit: $(basename "$unit")"
    sudo install -m 0644 "$unit" "$SYSTEMD_DIR/$(basename "$unit")"
  done
  if $SYSTEMD_CHANGED; then
    sudo systemctl daemon-reload
    if [ -f systemd/teleo-auto-deploy.timer ]; then
      sudo systemctl enable --now teleo-auto-deploy.timer >/dev/null
    fi
    if [ -f systemd/teleo-agent-healthcheck.timer ]; then
      sudo systemctl enable --now teleo-agent-healthcheck.timer >/dev/null
    fi
  else
    log "Systemd units already match the repository; skipping privileged installation"
  fi
fi

# Restart services only when changed files affect a running process.
RESTART=""
add_restart() {
  case " $RESTART " in
    *" $1 "*) ;;
    *) RESTART="$RESTART $1" ;;
  esac
}
add_restart_if_unit_exists() {
  if systemctl list-units --all --full "$1.service" --no-legend 2>/dev/null | grep -q .; then
    add_restart "$1"
  fi
}
add_restart_if_unit_active() {
  if systemctl is-active --quiet "$1.service"; then
    add_restart "$1"
  fi
}
if [ "$OLD_SHA" != "none" ]; then
  if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- lib/ teleo-pipeline.py reweave.py telegram/ 2>/dev/null | grep -q '\.py$'; then
    add_restart teleo-pipeline
  fi
  if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- telegram/ 2>/dev/null | grep -q '\.py$'; then
    add_restart_if_unit_active teleo-agent@leo
    add_restart_if_unit_exists teleo-agent@leo-wallet-test
  fi
  if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- systemd/teleo-agent@.service 2>/dev/null | grep -q .; then
    add_restart_if_unit_active teleo-agent@leo
    add_restart_if_unit_exists teleo-agent@leo-wallet-test
  fi
  if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- diagnostics/ 2>/dev/null | grep -q '\.py$'; then
    add_restart teleo-diagnostics
  fi
  if git diff --name-only "$OLD_SHA" "$NEW_SHA" -- hermes-agent/leoclean-bin/ hermes-agent/leoclean-skills/vps/ hermes-agent/leoclean-plugins/vps/ hermes-agent/patches/ 2>/dev/null \
    | bash deploy/leoclean-gateway-restart-required.sh; then
    add_restart_if_unit_active leoclean-gateway
  fi
  if $LEOCLEAN_POLICY_CHANGED; then
    add_restart_if_unit_active leoclean-gateway
  fi
else
  RESTART="teleo-pipeline teleo-diagnostics"
  add_restart_if_unit_active teleo-agent@leo
  add_restart_if_unit_exists teleo-agent@leo-wallet-test
  add_restart_if_unit_active leoclean-gateway
fi

if [ -n "$RESTART" ]; then
  log "Restarting:$RESTART"
  for svc in $RESTART; do
    sudo systemctl restart "$svc"
  done
  sleep 30

  FAIL=0
  for svc in $RESTART; do
    if systemctl is-active --quiet "$svc"; then
      log "$svc: active"
    else
      log "ERROR: $svc failed to start"
      journalctl -u "$svc" -n 5 --no-pager 2>/dev/null || true
      FAIL=1
    fi
    if [ "$svc" = "leoclean-gateway" ] && [ -f "$HERMES_PATCH_DIR/apply_response_transform_hook.py" ]; then
      if PATCH_CHECK=$(python3 "$HERMES_PATCH_DIR/apply_response_transform_hook.py" --check "$HERMES_AGENT_DIR/run_agent.py"); then
        log "leoclean-gateway response transform: $PATCH_CHECK"
      else
        log "ERROR: leoclean-gateway response transform check failed: $PATCH_CHECK"
        FAIL=1
      fi
    fi
    if [ "$svc" = "leoclean-gateway" ] && [ -f "$LEOCLEAN_POLICY_PATCH" ]; then
      if POLICY_CHECK=$(python3 "$LEOCLEAN_POLICY_PATCH" --check "$LEOCLEAN_CONFIG"); then
        log "leoclean-gateway learning policy: $POLICY_CHECK"
      else
        log "ERROR: leoclean-gateway learning policy check failed: $POLICY_CHECK"
        FAIL=1
      fi
    fi
  done

  if echo "$RESTART" | grep -q "teleo-pipeline"; then
    HEALTH_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 http://localhost:8080/health 2>/dev/null || echo "000")
    if [ "$HEALTH_CODE" = "200" ] || [ "$HEALTH_CODE" = "503" ]; then
      log "pipeline health: OK (HTTP $HEALTH_CODE)"
    else
      log "WARNING: pipeline health check failed (HTTP $HEALTH_CODE)"
      FAIL=1
    fi
  fi

  if echo "$RESTART" | grep -q "teleo-diagnostics"; then
    if curl -sf --connect-timeout 3 http://localhost:8081/ops > /dev/null 2>&1; then
      log "diagnostics health: OK"
    else
      log "WARNING: diagnostics health check failed"
      FAIL=1
    fi
  fi

  if [ "$FAIL" -gt 0 ]; then
    log "WARNING: Smoke test failures. NOT updating stamp. Will retry next cycle. Push a fix."
    exit 1
  fi
else
  log "No runtime changes - services not restarted"
fi

echo "$NEW_SHA" > "$STAMP_FILE"
log "Deploy complete: $(git log --oneline -1 "$NEW_SHA")"
