#!/usr/bin/env bash
# Polls a CloudWatch Logs group (e.g. AWS API Gateway access logs) and appends
# each event, reshaped to the Log Parser Azure API Gateway format, to a
# newline-delimited JSON file the Log Parser tails.
#
# Configure via environment variables (all but LOG_GROUP and REGION are optional):
#   LOG_GROUP        CloudWatch log group name (required)
#   REGION           AWS region of the log group (required)
#   OUT_DIR          Directory to write into (default: ./logs/azure)
#   POLL_INTERVAL    Seconds between polls (default: 10)
set -euo pipefail

: "${LOG_GROUP:?Set LOG_GROUP to your CloudWatch log group name}"
: "${REGION:?Set REGION to the AWS region of your CloudWatch log group}"
OUT_DIR="${OUT_DIR:-./logs/azure}"
POLL_INTERVAL="${POLL_INTERVAL:-10}"

OUT_FILE="$OUT_DIR/api-gateway.json"
STATE_FILE="$OUT_DIR/.sync-cloudwatch-logs.state"
LOCK_FILE="${TMPDIR:-/tmp}/sync-cloudwatch-logs.lock"
INITIAL_LOOKBACK_MS=120000   # only used the very first time, no state file yet

mkdir -p "$OUT_DIR"

log() { printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >&2; }

# Single-instance guard - without this, two copies writing to $OUT_FILE at
# once can interleave mid-line and corrupt the file.
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
  log "another sync instance is already running (lock: $LOCK_FILE) - exiting"
  exit 1
fi

# Resume from the last event processed instead of a fixed rolling window,
# which would re-ingest the same events on every poll.
if [[ -f "$STATE_FILE" ]]; then
  start_time_ms=$(<"$STATE_FILE")
else
  start_time_ms=$(( $(date +%s%3N) - INITIAL_LOOKBACK_MS ))
fi

while true; do
  end_time_ms=$(date +%s%3N)

  # Paginate - filter-log-events truncates a single call's results and hands
  # back a nextToken when there is more.
  next_token=""
  all_messages=""
  max_ts=""
  while :; do
    if [[ -n "$next_token" ]]; then
      resp=$(aws logs filter-log-events \
        --log-group-name "$LOG_GROUP" --region "$REGION" \
        --start-time "$start_time_ms" --end-time "$end_time_ms" \
        --next-token "$next_token" --output json 2>&1) || { log "aws cli failed: $resp"; resp=""; break; }
    else
      resp=$(aws logs filter-log-events \
        --log-group-name "$LOG_GROUP" --region "$REGION" \
        --start-time "$start_time_ms" --end-time "$end_time_ms" \
        --output json 2>&1) || { log "aws cli failed: $resp"; resp=""; break; }
    fi

    all_messages+=$(jq -r '.events[].message' <<<"$resp")$'\n'
    page_max=$(jq -r '[.events[].timestamp] | max // empty' <<<"$resp")
    [[ -n "$page_max" ]] && max_ts="$page_max"
    next_token=$(jq -r '.nextToken // empty' <<<"$resp")
    [[ -n "$next_token" ]] || break
  done

  # Build the whole batch in memory first; parse each message independently
  # so one malformed line cannot abort the rest.
  batch=""
  while IFS= read -r message; do
    [[ $message == '{'* ]] || continue
    if line=$(jq -c '
          select(type == "object") |
          {
            callerIpAddress: .ip,
            host: .host,
            agent: .agent,
            properties: {
              method: .method,
              url: .path,
              responseCode: .code,
              responseSize: .responseLength
            }
          }
        ' <<<"$message" 2>/dev/null); then
      batch+="$line"$'\n'
    else
      log "skipping malformed event: $message"
    fi
  done <<<"$all_messages"

  # Single write() syscall for the whole batch, so it cannot be torn or
  # interleaved with another writer even under load.
  if [[ -n "$batch" ]]; then
    printf '%s' "$batch" >> "$OUT_FILE"
  fi

  # Advance the watermark so the next poll never re-requests this window.
  start_time_ms=$(( ${max_ts:-$end_time_ms} + 1 ))
  printf '%s' "$start_time_ms" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"

  sleep "$POLL_INTERVAL"
done
