#!/usr/bin/env bash
# Two local Coolify instances (isolated stacks) for multi-control-plane testing.
#
# Usage:
#   ./scripts/dev-instances up              # start a + b
#   ./scripts/dev-instances up a            # start a only
#   ./scripts/dev-instances up a --with vite   # HMR only for a single instance
#   ./scripts/dev-instances down               # stop a + b
#   ./scripts/dev-instances urls
#   ./scripts/dev-instances ps
#   ./scripts/dev-instances logs a
#   ./scripts/dev-instances exec a php artisan migrate --force
#
# Ports (fixed):
#   a  app 8000  db 5432  redis 6379  soketi 6001/6002  flux 6443
#   b  app 8001  db 5433  redis 6380  soketi 6011/6012  flux 6444
#
# Frontend: multi-instance uses npm run build (shared public/build). Do not use
# two Vite HMR servers — public/hot is a single shared file.
#
# Compose: docker-compose.dev-multi.yml
# Env:     .dev-instances/{a,b}.env  (generated, gitignored)
#
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"

COMPOSE_FILE="docker-compose.dev-multi.yml"
ENV_DIR=".dev-instances"
INSTANCES_ALL=(a b)

usage() {
  sed -n '2,18p' "$0" | sed 's/^# \?//'
  exit "${1:-0}"
}

# Fixed port offsets: a=0, b=1
instance_offset() {
  case "$1" in
    a|1) echo 0 ;;
    b|2) echo 1 ;;
    *)
      echo "Unknown instance '$1' (use a or b)" >&2
      exit 1
      ;;
  esac
}

normalize_name() {
  case "$1" in
    a|1) echo a ;;
    b|2) echo b ;;
    *)
      echo "Unknown instance '$1' (use a or b)" >&2
      exit 1
      ;;
  esac
}

project_name() { echo "coolify-$1"; }
env_file() { echo "${ENV_DIR}/$1.env"; }

ensure_env() {
  local name="$1"
  local offset env_file app_port existing_key key
  offset="$(instance_offset "$name")"
  env_file="$(env_file "$name")"
  app_port=$((8000 + offset))
  mkdir -p "$ENV_DIR"

  # Never rotate APP_KEY once set: encrypted DB columns (private keys, secrets)
  # become unreadable ("The MAC is invalid") if APP_KEY changes while volumes persist.
  existing_key=""
  if [[ -f "$env_file" ]]; then
    existing_key="$(grep -E '^APP_KEY=base64:' "$env_file" 2>/dev/null | tail -n1 | cut -d= -f2- || true)"
  fi
  if [[ -z "$existing_key" && -f ".dev-instances/${name}.appkey" ]]; then
    existing_key="$(cat ".dev-instances/${name}.appkey" 2>/dev/null || true)"
  fi
  if [[ -z "$existing_key" ]]; then
    existing_key="base64:$(openssl rand -base64 32)"
  fi
  printf '%s\n' "$existing_key" >".dev-instances/${name}.appkey"

  cat >"$env_file" <<EOF
# Generated by scripts/dev-instances for instance ${name} — do not commit
COMPOSE_PROJECT_NAME=coolify-${name}
APP_NAME=Coolify-${name}
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:${app_port}
APP_KEY=${existing_key}
APP_PORT=${app_port}
FORWARD_DB_PORT=$((5432 + offset))
FORWARD_REDIS_PORT=$((6379 + offset))
FORWARD_SOKETI_PORT=$((6001 + offset * 10))
FORWARD_SOKETI_PORT_ALT=$((6002 + offset * 10))
FORWARD_FLUX_PORT=$((6443 + offset))
VITE_PORT=$((5173 + offset))
FORWARD_MAILPIT_PORT=$((1025 + offset * 100))
FORWARD_MAILPIT_DASHBOARD_PORT=$((8025 + offset * 100))
FORWARD_MINIO_PORT=$((9000 + offset * 10))
FORWARD_MINIO_PORT_CONSOLE=$((9001 + offset * 10))
DB_DATABASE=coolify
DB_USERNAME=coolify
DB_PASSWORD=password
PUSHER_APP_ID=coolify-${name}
PUSHER_APP_KEY=coolify-${name}
PUSHER_APP_SECRET=coolify-${name}
EOF
}

compose() {
  local name="$1"
  shift
  local -a profiles=()
  local p
  for p in "${PROFILES[@]:-}"; do
    [[ -n "$p" ]] && profiles+=(--profile "$p")
  done
  ensure_env "$name"
  docker compose -p "$(project_name "$name")" -f "$COMPOSE_FILE" --env-file "$(env_file "$name")" "${profiles[@]}" "$@"
}

# Parse: [a] [b] [--with vite mailpit ...]
# Default instances when none given: a b
parse_args() {
  NAMES=()
  PROFILES=()
  local mode=names
  local arg
  for arg in "$@"; do
    if [[ "$arg" == "--with" ]]; then
      mode=profiles
      continue
    fi
    if [[ "$mode" == "profiles" ]]; then
      PROFILES+=("$arg")
    else
      NAMES+=("$(normalize_name "$arg")")
    fi
  done
  if [[ ${#NAMES[@]} -eq 0 ]]; then
    NAMES=("${INSTANCES_ALL[@]}")
  fi
}

# Multi-instance stacks share the repo bind-mount. Laravel's public/hot can only
# point at one Vite server, so dual HMR is broken (last Vite wins → missing CSS).
# Use a production frontend build shared by all instances instead.
ensure_frontend_assets() {
  if [[ -f public/hot ]]; then
    echo "Removing public/hot (multi-instance cannot share Vite HMR)..."
    rm -f public/hot
  fi
  if [[ ! -d node_modules ]]; then
    echo "Installing npm dependencies..."
    npm install
  fi
  if [[ ! -f public/build/manifest.json ]]; then
    echo "Building frontend assets (npm run build)..."
    npm run build || {
      echo "Warning: npm run build failed — UI will have no CSS/JS." >&2
      return 1
    }
    return
  fi
  # Rebuild if CSS entry is missing from disk (stale/partial build).
  local css
  css="$(php -r '
    $m=@json_decode(@file_get_contents("public/build/manifest.json"), true);
    $f=$m["resources/css/app.css"]["file"] ?? "";
    echo $f && is_file("public/build/".$f) ? "ok" : "missing";
  ' 2>/dev/null || echo missing)"
  if [[ "$css" != "ok" ]]; then
    echo "Frontend build incomplete — running npm run build..."
    npm run build || echo "Warning: npm run build failed." >&2
  fi
}

# Vite profile is only useful for a single instance; drop it when starting both.
filter_vite_profile() {
  local -a kept=()
  local p want_vite=0
  for p in "${PROFILES[@]:-}"; do
    if [[ "$p" == "vite" ]]; then
      want_vite=1
      continue
    fi
    kept+=("$p")
  done
  if [[ $want_vite -eq 1 ]]; then
    if [[ ${#NAMES[@]} -gt 1 ]]; then
      echo "Note: ignoring --with vite (shared public/hot cannot serve two instances)."
      echo "      Frontend uses npm run build. For HMR, run a single instance: $0 up a --with vite"
    else
      kept+=("vite")
    fi
  fi
  PROFILES=("${kept[@]}")
}

cmd_up() {
  parse_args "$@"
  filter_vite_profile
  # Prefer built assets before containers serve HTML.
  ensure_frontend_assets || true
  local name
  local -a saved_profiles=("${PROFILES[@]:-}")
  for name in "${NAMES[@]}"; do
    PROFILES=("${saved_profiles[@]}")
    # Only the first (only) instance may run the vite profile.
    if [[ ${#NAMES[@]} -eq 1 ]] && printf '%s\n' "${PROFILES[@]:-}" | grep -qx vite; then
      :
    else
      local -a no_vite=()
      local p
      for p in "${PROFILES[@]:-}"; do
        [[ "$p" != "vite" ]] && no_vite+=("$p")
      done
      PROFILES=("${no_vite[@]}")
    fi
    echo "==> Starting ${name} → http://localhost:$((8000 + $(instance_offset "$name")))"
    compose "$name" up -d --build
  done
  # If a lone instance started vite, keep public/hot; otherwise force built assets.
  if [[ ${#NAMES[@]} -gt 1 ]] || ! printf '%s\n' "${saved_profiles[@]:-}" | grep -qx vite; then
    ensure_frontend_assets || true
  fi
  echo
  cmd_urls "${NAMES[@]}"
  echo
  echo "Frontend: production build (public/build). Both instances share the same assets."
}

cmd_down() {
  parse_args "$@"
  local name
  for name in "${NAMES[@]}"; do
    if [[ ! -f "$(env_file "$name")" ]]; then
      ensure_env "$name" >/dev/null
    fi
    echo "==> Stopping ${name}"
    compose "$name" down --remove-orphans
  done
}

cmd_ps() {
  parse_args "$@"
  local name
  for name in "${NAMES[@]}"; do
    [[ -f "$(env_file "$name")" ]] || ensure_env "$name" >/dev/null
    echo "==> $(project_name "$name")"
    compose "$name" ps
    echo
  done
}

cmd_urls() {
  local names=("$@")
  if [[ ${#names[@]} -eq 0 ]]; then
    names=("${INSTANCES_ALL[@]}")
  else
    parse_args "$@"
    names=("${NAMES[@]}")
  fi
  printf '%-6s %-28s %-8s %-8s %-12s\n' "NAME" "URL" "DB" "REDIS" "SOKETI"
  local name envf
  for name in "${names[@]}"; do
    name="$(normalize_name "$name")"
    ensure_env "$name" >/dev/null
    envf="$(env_file "$name")"
    printf '%-6s %-28s %-8s %-8s %-12s\n' \
      "$name" \
      "$(grep -E '^APP_URL=' "$envf" | cut -d= -f2-)" \
      "$(grep -E '^FORWARD_DB_PORT=' "$envf" | cut -d= -f2-)" \
      "$(grep -E '^FORWARD_REDIS_PORT=' "$envf" | cut -d= -f2-)" \
      "$(grep -E '^FORWARD_SOKETI_PORT=' "$envf" | cut -d= -f2-)"
  done
}

cmd_logs() {
  local name="${1:-}"
  shift || true
  [[ -z "$name" ]] && usage 1
  name="$(normalize_name "$name")"
  compose "$name" logs -f "$@"
}

cmd_exec() {
  local name="${1:-}"
  shift || true
  [[ -z "$name" || $# -eq 0 ]] && usage 1
  name="$(normalize_name "$name")"
  compose "$name" exec coolify "$@"
}

main() {
  local cmd="${1:-}"
  shift || true
  case "$cmd" in
    up) cmd_up "$@" ;;
    down) cmd_down "$@" ;;
    ps) cmd_ps "$@" ;;
    urls|ls|list) cmd_urls "$@" ;;
    logs) cmd_logs "$@" ;;
    exec) cmd_exec "$@" ;;
    -h|--help|help|"") usage 0 ;;
    *)
      echo "Unknown command: $cmd" >&2
      usage 1
      ;;
  esac
}

main "$@"
