#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BACKEND_DIR="$ROOT_DIR/backend"
WORKER_DIR="$BACKEND_DIR/worker"
WEB_DIR="$BACKEND_DIR/web"
ENV_FILE="$ROOT_DIR/deploy/.env.dev"
RUN_DIR="$ROOT_DIR/.run"
PID_FILE="$RUN_DIR/dev-all.pids"

mkdir -p "$RUN_DIR"
: > "$PID_FILE"

PIDS=()

cleanup() {
  local pid
  for pid in "${PIDS[@]:-}"; do
    if kill -0 "$pid" 2>/dev/null; then
      # Each job was started via `setsid`, so its PID is also its process
      # group id; killing the negative pid takes down the whole subtree
      # (e.g. npm -> tsx -> node) instead of leaving orphaned children.
      kill -TERM -- "-$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
    fi
  done
  wait 2>/dev/null || true
  rm -f "$PID_FILE"
}
trap cleanup EXIT INT TERM

load_env() {
  if [[ ! -f "$ENV_FILE" ]]; then
    echo "[dev-all] 找不到 $ENV_FILE，請先：cp deploy/.env.dev.example deploy/.env.dev 並填值" >&2
    exit 1
  fi
  set -a
  # shellcheck disable=SC1090
  source "$ENV_FILE"
  set +a

  : "${HAIXUN_MONGO_URI:?請在 $ENV_FILE 設定 HAIXUN_MONGO_URI}"
  : "${HAIXUN_REDIS_ADDR:?請在 $ENV_FILE 設定 HAIXUN_REDIS_ADDR}"
  : "${HAIXUN_REDIS_PASSWORD:?請在 $ENV_FILE 設定 HAIXUN_REDIS_PASSWORD}"
  : "${HAIXUN_JWT_ACCESS_SECRET:?請在 $ENV_FILE 設定 HAIXUN_JWT_ACCESS_SECRET}"
  : "${HAIXUN_JWT_REFRESH_SECRET:?請在 $ENV_FILE 設定 HAIXUN_JWT_REFRESH_SECRET}"
  : "${HAIXUN_WORKER_SECRET:?請在 $ENV_FILE 設定 HAIXUN_WORKER_SECRET}"
  : "${HAIXUN_MONGO_DB:=haixun}"
  : "${HAIXUN_SECRETS_KEY:=}"
  : "${HAIXUN_BACKEND_URL:=http://127.0.0.1:8890}"
  : "${HAIXUN_WORKER_POLL_MS:=3000}"
  : "${HAIXUN_8D_TARGET_SAMPLES:=4}"
  : "${PLAYWRIGHT_HEADLESS:=true}"

  export HAIXUN_MONGO_URI HAIXUN_MONGO_DB HAIXUN_REDIS_ADDR HAIXUN_REDIS_PASSWORD
  export HAIXUN_JWT_ACCESS_SECRET HAIXUN_JWT_REFRESH_SECRET HAIXUN_WORKER_SECRET
  export HAIXUN_SECRETS_KEY HAIXUN_BACKEND_URL
  export HAIXUN_WORKER_POLL_MS HAIXUN_8D_TARGET_SAMPLES
  export THREADS_APP_ID THREADS_APP_SECRET THREADS_REDIRECT_URI THREADS_OAUTH_SUCCESS_REDIRECT
  export PLAYWRIGHT_HEADLESS
}

start() {
  local name="$1"
  shift
  echo "[dev-all] starting $name"
  # setsid makes the launched process its own session/process-group leader,
  # so dev-all-stop.sh can reliably kill it *and* every child it spawns
  # (npm -> tsx -> node, etc.) with a single `kill -- -PID`, even if this
  # script itself gets killed abruptly and never runs its own cleanup trap.
  setsid "$@" &
  local pid="$!"
  PIDS+=("$pid")
  echo "$pid $name" >> "$PID_FILE"
}

port_in_use() {
  local port="$1"
  ss -tlnp 2>/dev/null | grep -qE ":${port} "
}

free_port() {
  local port="$1"
  if ! port_in_use "$port"; then
    return 0
  fi
  echo "[dev-all] freeing port :$port"
  if command -v fuser >/dev/null 2>&1; then
    fuser -k "${port}/tcp" 2>/dev/null || true
  else
    local line pid
    while IFS= read -r line; do
      for pid in $(grep -oE 'pid=[0-9]+' <<<"$line" | cut -d= -f2); do
        kill "$pid" 2>/dev/null || true
      done
    done < <(ss -tlnp 2>/dev/null | grep -E ":${port} " || true)
  fi
  sleep 0.3
}

ensure_http_service() {
  local name="$1"
  local port="$2"
  local health_url="$3"
  shift 3

  if curl -fsS "$health_url" >/dev/null 2>&1; then
    echo "[dev-all] reusing $name (already healthy on :$port)"
    return 0
  fi
  if port_in_use "$port"; then
    echo "[dev-all] port :$port occupied but $name unhealthy; restarting"
    free_port "$port"
  fi
  start "$name" "$@"
  wait_for_http "$name" "$health_url"
}

wait_for_http() {
  local name="$1"
  local url="$2"
  local i
  for i in {1..40}; do
    if curl -fsS "$url" >/dev/null 2>&1; then
      echo "[dev-all] ready $name"
      return 0
    fi
    sleep 0.5
  done
  echo "[dev-all] $name did not become ready: $url" >&2
  return 1
}

pick_web_port() {
  python3 - <<'PY'
import socket

for port in range(5173, 5200):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.settimeout(0.1)
        if sock.connect_ex(('127.0.0.1', port)) != 0:
            print(port)
            raise SystemExit(0)
raise SystemExit('no free Vite port found in 5173..5199')
PY
}

load_env

cd "$BACKEND_DIR"
mkdir -p bin
go build -o bin/gateway gateway.go
go build -o bin/worker ./cmd/worker

cd "$WORKER_DIR"
npm install

cd "$WEB_DIR"
npm install

cd "$BACKEND_DIR"
ensure_http_service "gateway" 8890 "http://127.0.0.1:8890/api/v1/health" \
  ./bin/gateway -f etc/gateway.yaml

GO_WORKER_TYPES=(
  go-demo
  go-expand-graph
  go-placement-scan
  go-scan-viral
  go-analyze-copy-mission
  go-expand-copy-mission-graph
  go-generate-copy-matrix
  go-generate-copy-draft
  go-generate-outreach-draft
  go-generate-topic-matrix
  go-generate-formula-draft
  go-generate-rewrite-draft
  go-refresh-threads-token
  go-publish-analytics
  go-refill-publish-inventory
)

for worker_type in "${GO_WORKER_TYPES[@]}"; do
  start "worker:$worker_type" env \
    HAIXUN_JOB_WORKER_TYPE="$worker_type" \
    HAIXUN_JOB_WORKER_ID="local-$worker_type" \
    ./bin/worker -f etc/gateway.worker.yaml
done

cd "$WORKER_DIR"
ensure_http_service "node-tool:keyword-search" 9101 "http://127.0.0.1:9101/health" \
  env PORT=9101 npm run keyword-search-server
ensure_http_service "node-tool:resolve-media" 9102 "http://127.0.0.1:9102/health" \
  env PORT=9102 npm run resolve-media-server
start "node-worker:style-8d" env \
  HAIXUN_NODE_WORKER_TYPE=node-style-8d \
  HAIXUN_NODE_WORKER_ID=local-node-style-8d \
  npm run style-8d

cd "$WEB_DIR"
WEB_PORT="${WEB_PORT:-$(pick_web_port)}"
start "web" npm run dev -- --host 0.0.0.0 --port "$WEB_PORT" --strictPort
wait_for_http "web" "http://127.0.0.1:$WEB_PORT/"

echo
echo "[dev-all] all services are running"
echo "[dev-all] API: http://127.0.0.1:8890/api/v1/health"
echo "[dev-all] Web: http://127.0.0.1:$WEB_PORT/"
echo "[dev-all] Press Ctrl+C to stop"

wait -n
