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

# Eyesis_Correction lifecycle helper for MCP-driven automation.
#
# Default behavior follows the Eclipse-equivalent run settings from AGENTS.md:
#   -Xmx80000m -ea -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true
#
# Override settings with env vars or command options.

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

STATE_DIR="${EYESIS_MCP_STATE_DIR:-${REPO_ROOT}/attic/session-logs}"
PID_FILE="${STATE_DIR}/eyesis_mcp.pid"
LOG_FILE="${EYESIS_MCP_LOG_FILE:-${STATE_DIR}/eyesis_mcp.log}"

PORT="${EYESIS_MCP_PORT:-48888}"
ALLOWED_CONFIGDIR="${EYESIS_MCP_ALLOWED_CONFIGDIR:-/media/elphel/btrfs-data/lwir16-proc/NC/config}"
ALLOWED_CONFIG="${EYESIS_MCP_ALLOWED_CONFIG:-}"
ALLOWED_EXTRA="${EYESIS_MCP_ALLOWED_EXTRA:-}"

MCP_MODE="${EYESIS_MCP_MODE:-true}"
MCP_DIALOG_TIMEOUT_MS="${EYESIS_MCP_DIALOG_TIMEOUT_MS:-0}"
SLF4J_LEVEL="${EYESIS_SLF4J_LEVEL:-ERROR}"
WAIT_MCP_TIMEOUT_SEC="${EYESIS_MCP_WAIT_SEC:-120}"
JAVA_BIN="${EYESIS_MCP_JAVA_BIN:-java}"

BASE_JVM_ARGS="${EYESIS_MCP_JVM_ARGS:-"-Xmx80000m -ea -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true"}"
FORCE_STOP=0
DO_REBUILD=0
DRY_RUN=0

usage() {
  cat <<'USAGE'
Usage:
  scripts/eyesis_mcp_ctl.sh start [options]
  scripts/eyesis_mcp_ctl.sh stop [options]
  scripts/eyesis_mcp_ctl.sh restart [options]
  scripts/eyesis_mcp_ctl.sh status
  scripts/eyesis_mcp_ctl.sh wait [options]
  scripts/eyesis_mcp_ctl.sh logs [--lines N]

Options:
  --port N              MCP port (default: env EYESIS_MCP_PORT or 48888)
  --configdir PATH      -Delphel.mcp.allowed.configdir value
  --config PATH         -Delphel.mcp.allowed.config value
  --extra PATHS         -Delphel.mcp.allowed.extra value
  --log PATH            Log file path
  --rebuild             Run 'mvn -DskipTests clean package' before start/restart
  --dry-run             Print resolved command and exit (start/restart only)
  --wait-sec N          MCP wait timeout in seconds for start/wait
  --force               Force kill on stop if TERM timeout expires
  --lines N             Number of lines for 'logs' (default: 80)
  -h, --help            Show this help

Environment overrides:
  EYESIS_MCP_STATE_DIR, EYESIS_MCP_LOG_FILE, EYESIS_MCP_PORT,
  EYESIS_MCP_ALLOWED_CONFIGDIR, EYESIS_MCP_ALLOWED_CONFIG, EYESIS_MCP_ALLOWED_EXTRA,
  EYESIS_MCP_MODE, EYESIS_MCP_DIALOG_TIMEOUT_MS, EYESIS_SLF4J_LEVEL,
  EYESIS_MCP_WAIT_SEC, EYESIS_MCP_JVM_ARGS, EYESIS_MCP_JAVA_BIN
USAGE
}

ensure_state_dir() {
  mkdir -p "${STATE_DIR}"
}

is_running() {
  if [[ ! -f "${PID_FILE}" ]]; then
    return 1
  fi
  local pid
  pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
  if [[ -z "${pid}" ]]; then
    return 1
  fi
  if kill -0 "${pid}" 2>/dev/null; then
    return 0
  fi
  return 1
}

clear_stale_pid() {
  if [[ -f "${PID_FILE}" ]] && ! is_running; then
    rm -f "${PID_FILE}"
  fi
}

mcp_status_url() {
  echo "http://127.0.0.1:${PORT}/mcp/status"
}

mcp_base_url() {
  echo "http://127.0.0.1:${PORT}/mcp"
}

mcp_is_up() {
  curl -fsS "$(mcp_status_url)" >/dev/null 2>&1
}

mcp_request_exit() {
  local helper="${REPO_ROOT}/scripts/mcp_http.sh"
  if [[ -x "${helper}" ]]; then
    "${helper}" button --timeout 5 --label "Exit" >/dev/null
    return $?
  fi
  curl -fsS --max-time 5 -X POST --data-urlencode "label=Exit" "$(mcp_base_url)/button" >/dev/null
}

print_status() {
  clear_stale_pid
  local mcp_up=0
  if mcp_is_up; then
    mcp_up=1
  fi
  if is_running; then
    local pid
    pid="$(cat "${PID_FILE}")"
    echo "process: running (pid=${pid})"
  else
    echo "process: stopped"
    if [[ "${mcp_up}" -eq 1 ]]; then
      echo "note: MCP is up but pid file is absent/stale (instance likely started outside this script)"
    fi
  fi
  if [[ "${mcp_up}" -eq 1 ]]; then
    echo "mcp: up on port ${PORT}"
    curl -fsS "$(mcp_status_url)" || true
    echo
  else
    echo "mcp: down on port ${PORT}"
  fi
  echo "log: ${LOG_FILE}"
}

wait_for_mcp() {
  local timeout="${1}"
  local i
  for ((i = 0; i < timeout; i++)); do
    if mcp_is_up; then
      return 0
    fi
    if ! is_running; then
      return 2
    fi
    sleep 1
  done
  return 1
}

build_if_requested() {
  local need_build=0
  if [[ "${DO_REBUILD}" -eq 1 ]]; then
    need_build=1
  elif [[ ! -f "${REPO_ROOT}/target/imagej-elphel-1.0.0-SNAPSHOT.jar" ]]; then
    need_build=1
  fi

  if [[ "${need_build}" -eq 1 ]]; then
    (cd "${REPO_ROOT}" && mvn -DskipTests clean package)
  fi
}

compose_jvm_args() {
  local args
  args="${BASE_JVM_ARGS}"
  args+=" -Delphel.mcp.mode=${MCP_MODE}"
  args+=" -Delphel.mcp.port=${PORT}"
  args+=" -Delphel.mcp.dialogTimeoutMs=${MCP_DIALOG_TIMEOUT_MS}"
  args+=" -Delphel.slf4j.level=${SLF4J_LEVEL}"
  if [[ -n "${ALLOWED_CONFIG}" ]]; then
    args+=" -Delphel.mcp.allowed.config=${ALLOWED_CONFIG}"
  fi
  if [[ -n "${ALLOWED_CONFIGDIR}" ]]; then
    args+=" -Delphel.mcp.allowed.configdir=${ALLOWED_CONFIGDIR}"
  fi
  if [[ -n "${ALLOWED_EXTRA}" ]]; then
    args+=" -Delphel.mcp.allowed.extra=${ALLOWED_EXTRA}"
  fi
  echo "${args}"
}

warn_if_display_unavailable() {
  if [[ -z "${DISPLAY:-}" ]]; then
    echo "Warning: DISPLAY is not set. GUI startup may fail." >&2
    return
  fi
  if command -v xdpyinfo >/dev/null 2>&1; then
    if ! xdpyinfo >/dev/null 2>&1; then
      echo "Warning: DISPLAY=${DISPLAY} is not reachable from this shell." >&2
    fi
  fi
}

get_manifest_classpath() {
  local jar_path="$1"
  unzip -p "${jar_path}" META-INF/MANIFEST.MF 2>/dev/null | tr -d "\r" | awk '
    BEGIN { collecting = 0; done = 0; line = "" }
    /^Class-Path:[[:space:]]*/ {
      collecting = 1
      sub(/^Class-Path:[[:space:]]*/, "", $0)
      line = $0
      next
    }
    collecting && /^ / {
      sub(/^ /, "", $0)
      line = line $0
      next
    }
    collecting {
      done = 1
      print line
      exit
    }
    END {
      if (collecting && !done) {
        print line
      }
    }'
}

build_runtime_classpath() {
  local jar_path="${REPO_ROOT}/target/imagej-elphel-1.0.0-SNAPSHOT.jar"
  if [[ ! -f "${jar_path}" ]]; then
    echo "Missing ${jar_path}. Run with --rebuild first." >&2
    return 1
  fi

  local manifest_cp
  manifest_cp="$(get_manifest_classpath "${jar_path}")"
  if [[ -z "${manifest_cp}" ]]; then
    echo "Could not read Class-Path from ${jar_path} manifest." >&2
    return 1
  fi

  local repo="${HOME}/.m2/repository"
  if [[ ! -d "${repo}" ]]; then
    echo "Missing Maven local repository: ${repo}" >&2
    return 1
  fi

  declare -A by_name=()
  while IFS= read -r path; do
    local base
    base="${path##*/}"
    if [[ -z "${by_name[${base}]:-}" ]]; then
      by_name["${base}"]="${path}"
    fi
  done < <(find "${repo}" -type f -name '*.jar')

  local cp="${REPO_ROOT}/target/classes:${jar_path}"
  local missing=0
  local dep
  for dep in ${manifest_cp}; do
    local resolved="${by_name[${dep}]:-}"
    if [[ -z "${resolved}" ]]; then
      echo "Missing dependency jar in local Maven cache: ${dep}" >&2
      missing=1
      continue
    fi
    cp+=":${resolved}"
  done

  if [[ "${missing}" -ne 0 ]]; then
    return 1
  fi
  echo "${cp}"
}

start_process() {
  ensure_state_dir
  clear_stale_pid
  if mcp_is_up; then
    echo "Refusing to start: MCP is already up on port ${PORT}." >&2
    echo "Another Eyesis_Correction instance is likely running." >&2
    echo "Use 'scripts/eyesis_mcp_ctl.sh status' to inspect, then stop that instance first." >&2
    return 3
  fi
  if is_running; then
    echo "Already running (pid=$(cat "${PID_FILE}"))."
    return 0
  fi
  if [[ -z "${ALLOWED_CONFIG}" && -z "${ALLOWED_CONFIGDIR}" ]]; then
    echo "Refusing to start: neither --config nor --configdir is set." >&2
    return 2
  fi
  build_if_requested
  warn_if_display_unavailable

  local jvm_args
  jvm_args="$(compose_jvm_args)"
  local runtime_cp
  runtime_cp="$(build_runtime_classpath)"
  # shellcheck disable=SC2206
  local -a jvm_args_arr=(${jvm_args})
  local -a cmd=("${JAVA_BIN}" "${jvm_args_arr[@]}" -cp "${runtime_cp}" com.elphel.imagej.correction.Eyesis_Correction)

  echo "Starting Eyesis_Correction..."
  echo "Command: ${cmd[*]}"
  echo "Log: ${LOG_FILE}"

  if [[ "${DRY_RUN}" -eq 1 ]]; then
    echo "Dry-run requested, not starting process."
    return 0
  fi

  (
    cd "${REPO_ROOT}"
    nohup "${cmd[@]}" >>"${LOG_FILE}" 2>&1 &
    echo $! >"${PID_FILE}"
  )

  local rc=0
  if wait_for_mcp "${WAIT_MCP_TIMEOUT_SEC}"; then
    echo "Started and MCP is up on port ${PORT}."
    return 0
  else
    rc=$?
  fi

  if [[ "${rc}" -eq 2 ]]; then
    clear_stale_pid
    echo "Process exited before MCP became ready. Recent log tail:" >&2
    tail -n 60 "${LOG_FILE}" >&2 || true
    return 1
  fi
  echo "MCP did not become ready within ${WAIT_MCP_TIMEOUT_SEC}s (process is still running)." >&2
  return 1
}

stop_process() {
  ensure_state_dir
  clear_stale_pid

  # Prefer graceful in-app shutdown when MCP is reachable (covers script- and Eclipse-started instances).
  if mcp_is_up; then
    echo "Requesting clean shutdown via MCP Exit..."
    if mcp_request_exit; then
      local i
      for ((i = 0; i < 30; i++)); do
        clear_stale_pid
        if ! mcp_is_up && ! is_running; then
          rm -f "${PID_FILE}" 2>/dev/null || true
          echo "Stopped cleanly via MCP Exit."
          return 0
        fi
        sleep 1
      done
      echo "MCP Exit did not complete within timeout; falling back to signal stop." >&2
    else
      echo "Failed to send MCP Exit command; falling back to signal stop." >&2
    fi
  fi

  if ! is_running; then
    if mcp_is_up; then
      echo "MCP is still up, but no managed pid file is available. Instance is likely not script-managed." >&2
      return 1
    fi
    echo "Not running."
    return 0
  fi

  local pid
  pid="$(cat "${PID_FILE}")"
  echo "Stopping pid ${pid}..."
  kill -TERM "${pid}" 2>/dev/null || true

  local i
  for ((i = 0; i < 30; i++)); do
    if ! kill -0 "${pid}" 2>/dev/null; then
      rm -f "${PID_FILE}"
      echo "Stopped."
      return 0
    fi
    sleep 1
  done

  if [[ "${FORCE_STOP}" -eq 1 ]]; then
    echo "TERM timeout reached, force killing pid ${pid}..."
    kill -KILL "${pid}" 2>/dev/null || true
    sleep 1
    rm -f "${PID_FILE}"
    echo "Force-stopped."
    return 0
  fi

  echo "Failed to stop within timeout. Use --force." >&2
  return 1
}

show_logs() {
  local lines="${1:-80}"
  ensure_state_dir
  if [[ ! -f "${LOG_FILE}" ]]; then
    echo "Log file does not exist: ${LOG_FILE}" >&2
    return 1
  fi
  tail -n "${lines}" -f "${LOG_FILE}"
}

wait_only() {
  clear_stale_pid
  if ! is_running; then
    echo "Process is not running."
    return 1
  fi
  if wait_for_mcp "${WAIT_MCP_TIMEOUT_SEC}"; then
    echo "MCP is up on port ${PORT}."
    return 0
  fi
  echo "Timed out waiting for MCP (${WAIT_MCP_TIMEOUT_SEC}s)." >&2
  return 1
}

if [[ $# -lt 1 ]]; then
  usage
  exit 1
fi

if [[ "${1}" == "-h" || "${1}" == "--help" ]]; then
  usage
  exit 0
fi

COMMAND="$1"
shift

LOG_LINES=80
while [[ $# -gt 0 ]]; do
  case "$1" in
    --port)
      PORT="$2"
      shift 2
      ;;
    --configdir)
      ALLOWED_CONFIGDIR="$2"
      shift 2
      ;;
    --config)
      ALLOWED_CONFIG="$2"
      shift 2
      ;;
    --extra)
      ALLOWED_EXTRA="$2"
      shift 2
      ;;
    --log)
      LOG_FILE="$2"
      shift 2
      ;;
    --rebuild)
      DO_REBUILD=1
      shift
      ;;
    --dry-run)
      DRY_RUN=1
      shift
      ;;
    --wait-sec)
      WAIT_MCP_TIMEOUT_SEC="$2"
      shift 2
      ;;
    --force)
      FORCE_STOP=1
      shift
      ;;
    --lines)
      LOG_LINES="$2"
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "Unknown option: $1" >&2
      usage
      exit 2
      ;;
  esac
done

case "${COMMAND}" in
  start)
    start_process
    ;;
  stop)
    stop_process
    ;;
  restart)
    stop_process || true
    start_process
    ;;
  status)
    print_status
    ;;
  wait)
    wait_only
    ;;
  logs)
    show_logs "${LOG_LINES}"
    ;;
  *)
    echo "Unknown command: ${COMMAND}" >&2
    usage
    exit 2
    ;;
esac
