#!/usr/bin/env bash

set -euo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/../.." && pwd)"
local_bundle_dir="$repo_root/deploy/local-bundle"
compose_file="$local_bundle_dir/docker-compose.yml"
local_bundle_env_file="$local_bundle_dir/.env"

if [[ -f "$local_bundle_env_file" ]]; then
  set -a
  # shellcheck disable=SC1090
  source "$local_bundle_env_file"
  set +a
fi

config_repo_dir="${CONFIG_REPO_DIR:-$repo_root/config-repo}"
runtime_dir="${LOCAL_JAR_RUNTIME_DIR:-$repo_root/build/local-jar-runtime}"
pid_dir="$runtime_dir/pids"
log_dir="$runtime_dir/logs"

if [[ "$config_repo_dir" != /* ]]; then
  config_repo_dir="$repo_root/$config_repo_dir"
fi

if [[ "$runtime_dir" != /* ]]; then
  runtime_dir="$repo_root/$runtime_dir"
  pid_dir="$runtime_dir/pids"
  log_dir="$runtime_dir/logs"
fi

config_service="spring-cloud-config-server"
app_services=(
  pcp-srpring-boot-admin-server
  pcp-stations-service
  pcp-satellite-catalog-service
  pcp-request-service
  pcp-route-processing-service
  pcp-ballistics-service
  slots-service
  pcp-complex-mission-service
  pcp-mission-planing-service
  pcp-coverage-scheme-service
  pcp-dynamic-plan-service
  tle-monitoring-service
  pcp-tgu-service
  pcp-ui-service
)
all_services=("$config_service" "${app_services[@]}")

databases=(
  pcp_ballistics
  pcp_requests
  pcp_route_processing
  pcp_missions
  pcp_slots
  pcp_complex_plan
  pcp_satellite_catalog
  pcp_stations
  pcp_tgu
  pcp_tle
  pcp_satellites
)

declare -A service_ports=(
  [spring-cloud-config-server]="${CONFIG_SERVER_PORT:-38888}"
  [pcp-srpring-boot-admin-server]="${PCP_ADMIN_PORT:-7000}"
  [tle-monitoring-service]="${PCP_TLE_MONITORING_PORT:-7001}"
  [pcp-complex-mission-service]="${PCP_COMPLEX_MISSION_PORT:-7002}"
  [pcp-ballistics-service]="${PCP_BALLISTICS_PORT:-7003}"
  [pcp-route-processing-service]="${PCP_ROUTE_PROCESSING_PORT:-7004}"
  [pcp-request-service]="${PCP_REQUEST_PORT:-7005}"
  [slots-service]="${PCP_SLOTS_PORT:-7006}"
  [pcp-ui-service]="${PCP_UI_PORT:-7008}"
  [pcp-stations-service]="${PCP_STATIONS_PORT:-7009}"
  [pcp-mission-planing-service]="${PCP_MISSION_PORT:-7010}"
  [pcp-tgu-service]="${PCP_TGU_PORT:-7011}"
  [pcp-coverage-scheme-service]="${PCP_COVERAGE_SCHEME_PORT:-7012}"
  [pcp-satellite-catalog-service]="${PCP_SATELLITE_CATALOG_PORT:-7013}"
  [pcp-dynamic-plan-service]="${PCP_DYNAMIC_PLAN_PORT:-7014}"
)

gradle_max_workers="${GRADLE_MAX_WORKERS:-1}"
gradle_jvmargs="${GRADLE_JVMARGS:--Xmx2g -Dfile.encoding=UTF-8}"
kotlin_daemon_jvmargs="${KOTLIN_DAEMON_JVMARGS:--Xmx3g -XX:MaxMetaspaceSize=768m}"
config_server_port="${service_ports[$config_service]}"
config_server_uri="${LOCAL_CONFIG_SERVER_URI:-http://localhost:$config_server_port}"
service_profiles="${LOCAL_SERVICE_PROFILES:-local}"
config_profiles="${LOCAL_CONFIG_PROFILES:-$service_profiles}"
postgres_host="${PCP_POSTGRES_HOST:-localhost}"
postgres_port="${PCP_POSTGRES_PORT:-5432}"
kafka_host="${PCP_KAFKA_HOST:-localhost}"
kafka_port="${PCP_KAFKA_PORT:-29092}"

gradle_common_args=(
  "--no-parallel"
  "--max-workers=$gradle_max_workers"
  "-Dorg.gradle.jvmargs=$gradle_jvmargs"
  "-Pkotlin.daemon.jvmargs=$kotlin_daemon_jvmargs"
)

usage() {
  cat <<'EOF'
Usage:
  ./deploy/local-jar/run-services.sh [command]

Commands:
  start       Build bootJar artifacts and start local infra, config-server and services.
  restart     Stop local jar processes, then run start.
  stop        Stop jar processes started by this script.
  status      Show pid and log status for local jar processes.
  build       Build bootJar artifacts only.
  infra-up    Start local Postgres/Kafka via deploy/local-bundle/docker-compose.yml.
  infra-down  Stop local Postgres/Kafka containers.
  logs NAME   Tail log for a service.

Useful environment:
  START_INFRA=false       Do not start Postgres/Kafka containers during start.
  INIT_DB=false           Do not create local Postgres databases during start.
  WAIT_FOR_APPS=false     Do not wait for each app /actuator/health.
  CONFIG_REPO_DIR=...     Config repository used by spring-cloud-config-server.
  JAVA_OPTS="-Xmx1g"      Extra JVM options for every jar process.
  LOCAL_SERVICE_PROFILES=local,foo
EOF
}

require_command() {
  local command_name="$1"
  if ! command -v "$command_name" >/dev/null 2>&1; then
    echo "$command_name is required." >&2
    exit 1
  fi
}

compose() {
  if docker compose version >/dev/null 2>&1; then
    docker compose --project-directory "$local_bundle_dir" -f "$compose_file" "$@"
  elif command -v docker-compose >/dev/null 2>&1; then
    docker-compose --project-directory "$local_bundle_dir" -f "$compose_file" "$@"
  else
    echo "docker compose or docker-compose is required." >&2
    exit 1
  fi
}

http_get() {
  local url="$1"
  if command -v curl >/dev/null 2>&1; then
    curl -fsS "$url" >/dev/null
  elif command -v wget >/dev/null 2>&1; then
    wget -q -O /dev/null "$url"
  else
    return 2
  fi
}

wait_for_http() {
  local name="$1"
  local url="$2"
  local attempts="${3:-90}"
  local attempt

  for ((attempt = 1; attempt <= attempts; attempt++)); do
    if http_get "$url"; then
      echo "$name is ready: $url"
      return 0
    fi
    sleep 2
  done

  echo "$name is not ready after $((attempts * 2)) seconds: $url" >&2
  exit 1
}

wait_for_postgres() {
  local attempt
  local postgres_user="${POSTGRES_USER:-postgres}"

  for attempt in {1..60}; do
    if compose exec -T postgres pg_isready -U "$postgres_user" -d postgres >/dev/null 2>&1; then
      echo "Postgres is ready."
      return 0
    fi
    sleep 2
  done

  echo "Postgres is not ready after 120 seconds." >&2
  exit 1
}

wait_for_kafka() {
  local attempt
  for attempt in {1..60}; do
    if compose exec -T kafka /opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka:9092 --list >/dev/null 2>&1; then
      echo "Kafka is ready."
      return 0
    fi
    sleep 2
  done

  echo "Kafka is not ready after 120 seconds." >&2
  exit 1
}

create_database_if_missing() {
  local db_name="$1"
  local postgres_user="${POSTGRES_USER:-postgres}"
  local exists

  exists="$(
    compose exec -T postgres psql -U "$postgres_user" -d postgres -tAc \
      "SELECT 1 FROM pg_database WHERE datname = '$db_name'" | tr -d '[:space:]'
  )"

  if [[ "$exists" == "1" ]]; then
    echo "Database already exists: $db_name"
    return 0
  fi

  echo "Creating database: $db_name"
  compose exec -T postgres createdb -U "$postgres_user" "$db_name"
}

start_infra() {
  require_command docker
  echo "Starting local Postgres and Kafka..."
  compose up -d postgres kafka
  wait_for_postgres
  wait_for_kafka
}

init_databases() {
  echo "Initializing local service databases..."
  for db_name in "${databases[@]}"; do
    create_database_if_missing "$db_name"
  done
}

build_services() {
  echo "Building bootJar artifacts..."
  echo "Gradle max workers: $gradle_max_workers"
  echo "Gradle JVM args: $gradle_jvmargs"
  echo "Kotlin daemon JVM args: $kotlin_daemon_jvmargs"

  for service_name in "${all_services[@]}"; do
    echo "Building bootJar for $service_name..."
    "$repo_root/gradlew" "${gradle_common_args[@]}" ":services:$service_name:bootJar"
  done
}

service_pid_file() {
  local service_name="$1"
  printf '%s/%s.pid' "$pid_dir" "$service_name"
}

service_log_file() {
  local service_name="$1"
  printf '%s/%s.log' "$log_dir" "$service_name"
}

is_pid_running() {
  local pid="$1"
  [[ -n "$pid" ]] && kill -0 "$pid" >/dev/null 2>&1
}

read_service_pid() {
  local service_name="$1"
  local pid_file

  pid_file="$(service_pid_file "$service_name")"
  if [[ -f "$pid_file" ]]; then
    tr -d '[:space:]' < "$pid_file"
  fi
}

find_boot_jar() {
  local service_name="$1"
  local jar_dir="$repo_root/services/$service_name/build/libs"
  local jar_count
  local jar_file

  if [[ ! -d "$jar_dir" ]]; then
    echo "Missing build/libs for $service_name. Run build first." >&2
    exit 1
  fi

  jar_count="$(find "$jar_dir" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | wc -l)"
  if [[ "$jar_count" != "1" ]]; then
    echo "Expected exactly one bootJar for $service_name, found $jar_count:" >&2
    find "$jar_dir" -maxdepth 1 -type f -name '*.jar' -print >&2 || true
    exit 1
  fi

  jar_file="$(find "$jar_dir" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | head -n 1)"
  printf '%s' "$jar_file"
}

start_service() {
  local service_name="$1"
  local jar_file
  local pid_file
  local log_file
  local existing_pid
  local port="${service_ports[$service_name]}"
  local java_cmd=(java)
  local parsed_java_opts=()

  pid_file="$(service_pid_file "$service_name")"
  log_file="$(service_log_file "$service_name")"
  existing_pid="$(read_service_pid "$service_name" || true)"

  if is_pid_running "$existing_pid"; then
    echo "$service_name is already running with pid $existing_pid"
    return 0
  fi

  mkdir -p "$pid_dir" "$log_dir"
  jar_file="$(find_boot_jar "$service_name")"
  if [[ -n "${JAVA_OPTS:-}" ]]; then
    read -r -a parsed_java_opts <<< "$JAVA_OPTS"
    java_cmd+=("${parsed_java_opts[@]}")
  fi
  java_cmd+=(-jar "$jar_file")

  echo "Starting $service_name on port $port..."
  if [[ "$service_name" == "$config_service" ]]; then
    (
      cd "$repo_root"
      env \
        SPRING_PROFILES_ACTIVE=local \
        CONFIG_NATIVE_SEARCH_LOCATIONS="file:$config_repo_dir" \
        SERVER_PORT="$config_server_port" \
        "${java_cmd[@]}" > "$log_file" 2>&1 &
      echo "$!" > "$pid_file"
    )
  else
    (
      cd "$repo_root"
      env \
        CONFIG_SERVER_URI="$config_server_uri" \
        CONFIG_SERVER_FAIL_FAST=true \
        SPRING_PROFILES_ACTIVE="$service_profiles" \
        SPRING_CLOUD_CONFIG_PROFILE="$config_profiles" \
        SPRING_CLOUD_CONFIG_LABEL= \
        SPRING_KAFKA_ADMIN_AUTO_CREATE=true \
        SPRING_KAFKA_LISTENER_MISSING_TOPICS_FATAL=false \
        CAMUNDA_CLIENT_ENABLED=false \
        PLANNING_EXTERNAL_UPDATE_ENABLED=false \
        TLE_POLLING_ENABLED=false \
        PCP_POSTGRES_HOST="$postgres_host" \
        PCP_POSTGRES_PORT="$postgres_port" \
        PCP_KAFKA_HOST="$kafka_host" \
        PCP_KAFKA_PORT="$kafka_port" \
        PCP_PORTS_CONFIG="$config_server_port" \
        PCP_PORTS_ADMIN="${service_ports[pcp-srpring-boot-admin-server]}" \
        PCP_PORTS_TLE_MONITORING="${service_ports[tle-monitoring-service]}" \
        PCP_PORTS_COMPLEX_MISSION_SERVICE="${service_ports[pcp-complex-mission-service]}" \
        PCP_PORTS_BALLISTICS="${service_ports[pcp-ballistics-service]}" \
        PCP_PORTS_ROUTE_PROCESSING="${service_ports[pcp-route-processing-service]}" \
        PCP_PORTS_REQUEST="${service_ports[pcp-request-service]}" \
        PCP_PORTS_SLOTS="${service_ports[slots-service]}" \
        PCP_PORTS_UI="${service_ports[pcp-ui-service]}" \
        PCP_PORTS_STATIONS="${service_ports[pcp-stations-service]}" \
        PCP_PORTS_MISSION="${service_ports[pcp-mission-planing-service]}" \
        PCP_PORTS_TGU="${service_ports[pcp-tgu-service]}" \
        PCP_PORTS_COVERAGE_SCHEME="${service_ports[pcp-coverage-scheme-service]}" \
        PCP_PORTS_SATELLITE_CATALOG="${service_ports[pcp-satellite-catalog-service]}" \
        PCP_PORTS_DYNAMIC_PLAN="${service_ports[pcp-dynamic-plan-service]}" \
        "${java_cmd[@]}" > "$log_file" 2>&1 &
      echo "$!" > "$pid_file"
    )
  fi

  echo "$service_name log: $log_file"
}

start_config_server() {
  if [[ ! -d "$config_repo_dir" ]]; then
    echo "Config repo directory does not exist: $config_repo_dir" >&2
    exit 1
  fi

  start_service "$config_service"
  wait_for_http "$config_service" "$config_server_uri/actuator/health"
  wait_for_http "$config_service config-repo lookup" "$config_server_uri/pcp-request-service/local"
}

wait_for_service_health() {
  local service_name="$1"
  local port="${service_ports[$service_name]}"
  wait_for_http "$service_name" "http://localhost:$port/actuator/health" "${APP_HEALTH_ATTEMPTS:-90}"
}

start_app_services() {
  for service_name in "${app_services[@]}"; do
    start_service "$service_name"
    if [[ "${WAIT_FOR_APPS:-true}" == "true" ]]; then
      wait_for_service_health "$service_name"
    fi
  done
}

stop_service() {
  local service_name="$1"
  local pid_file
  local pid
  local attempt

  pid_file="$(service_pid_file "$service_name")"
  pid="$(read_service_pid "$service_name" || true)"
  if ! is_pid_running "$pid"; then
    rm -f "$pid_file"
    echo "$service_name is not running."
    return 0
  fi

  echo "Stopping $service_name with pid $pid..."
  kill "$pid" >/dev/null 2>&1 || true
  for attempt in {1..30}; do
    if ! is_pid_running "$pid"; then
      rm -f "$pid_file"
      return 0
    fi
    sleep 1
  done

  echo "Force stopping $service_name with pid $pid..."
  kill -9 "$pid" >/dev/null 2>&1 || true
  rm -f "$pid_file"
}

stop_services() {
  local service_name
  local idx

  for ((idx = ${#app_services[@]} - 1; idx >= 0; idx--)); do
    service_name="${app_services[$idx]}"
    stop_service "$service_name"
  done
  stop_service "$config_service"
}

status_services() {
  local service_name
  local pid
  local log_file

  printf '%-36s %-10s %s\n' "service" "pid" "log"
  for service_name in "${all_services[@]}"; do
    pid="$(read_service_pid "$service_name" || true)"
    log_file="$(service_log_file "$service_name")"
    if is_pid_running "$pid"; then
      printf '%-36s %-10s %s\n' "$service_name" "$pid" "$log_file"
    else
      printf '%-36s %-10s %s\n' "$service_name" "-" "$log_file"
    fi
  done
}

tail_logs() {
  local service_name="${1:-}"
  local log_file

  if [[ -z "$service_name" ]]; then
    echo "Service name is required for logs command." >&2
    exit 1
  fi

  log_file="$(service_log_file "$service_name")"
  if [[ ! -f "$log_file" ]]; then
    echo "Log file does not exist: $log_file" >&2
    exit 1
  fi

  tail -f "$log_file"
}

start_all() {
  require_command java

  build_services

  if [[ "${START_INFRA:-true}" == "true" ]]; then
    start_infra
    if [[ "${INIT_DB:-true}" == "true" ]]; then
      init_databases
    fi
  else
    echo "Skipping local infra startup. Expecting Postgres at $postgres_host:$postgres_port and Kafka at $kafka_host:$kafka_port."
  fi

  start_config_server
  start_app_services

  echo "Local jar startup requested."
  status_services
}

command="${1:-start}"
case "$command" in
  start)
    start_all
    ;;
  restart)
    stop_services
    start_all
    ;;
  stop)
    stop_services
    ;;
  status)
    status_services
    ;;
  build)
    build_services
    ;;
  infra-up)
    start_infra
    if [[ "${INIT_DB:-true}" == "true" ]]; then
      init_databases
    fi
    ;;
  infra-down)
    require_command docker
    compose stop kafka postgres
    ;;
  logs)
    tail_logs "${2:-}"
    ;;
  help|-h|--help)
    usage
    ;;
  *)
    usage >&2
    exit 1
    ;;
esac
