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

APP_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
STATE_DIR="${PIKOBS_WEB_STATE_DIR:-$HOME/.pikobs_web}"
PID_FILE="$STATE_DIR/pid"
PORT_FILE="$STATE_DIR/port"
TOKEN_FILE="$STATE_DIR/token"
HOST_FILE="$STATE_DIR/host"
LOG_FILE="$STATE_DIR/server.log"

mkdir -p "$STATE_DIR"
chmod 700 "$STATE_DIR"

choose_python() {
    if [ -n "${PIKOBS_WEB_PYTHON:-}" ]; then
        printf '%s\n' "$PIKOBS_WEB_PYTHON"
    elif [ -x "$APP_DIR/../env/bin/python" ]; then
        # Normal setup_pikobs.sh layout: PROJECT_DIR/pikobs_web beside PROJECT_DIR/env.
        printf '%s\n' "$APP_DIR/../env/bin/python"
    elif [ -x "$HOME/pikobs_install/env/bin/python" ]; then
        # Backward-compatible canonical location.
        printf '%s\n' "$HOME/pikobs_install/env/bin/python"
    else
        command -v python3 || command -v python
    fi
}

PYTHON="$(choose_python)"

check_web_python() {
    if [ -z "${PYTHON:-}" ] || ! "$PYTHON" -c 'import fastapi, uvicorn, pydantic' >/dev/null 2>&1; then
        cat >&2 <<MSG
ERROR: ${PYTHON:-python} does not have the Pikobs Web dependencies.
Set PIKOBS_WEB_PYTHON to a shared web Python/venv, or install once with:
  ${PYTHON:-python} -m pip install -r "$APP_DIR/requirements.txt"
MSG
        return 1
    fi
}

read_pid() {
    [ -s "$PID_FILE" ] || return 1
    local pid
    pid="$(cat "$PID_FILE" 2>/dev/null || true)"
    [[ "$pid" =~ ^[0-9]+$ ]] || return 1
    printf '%s\n' "$pid"
}

pid_alive() {
    local pid="$1"
    kill -0 "$pid" 2>/dev/null
}

pid_is_managed() {
    local pid="$1" args
    pid_alive "$pid" || return 1
    args="$(ps -p "$pid" -o args= 2>/dev/null || true)"
    [[ "$args" == *"uvicorn app.main:app"* ]]
}

state_complete() {
    [ -s "$PID_FILE" ] && [ -s "$PORT_FILE" ] && [ -s "$TOKEN_FILE" ] && [ -s "$HOST_FILE" ]
}

api_ready() {
    state_complete || return 1
    local port token
    port="$(cat "$PORT_FILE" 2>/dev/null || true)"
    token="$(cat "$TOKEN_FILE" 2>/dev/null || true)"
    [[ "$port" =~ ^[0-9]+$ ]] || return 1
    [ -n "$token" ] || return 1

    "$PYTHON" - "$port" "$token" <<'PY' >/dev/null 2>&1
import json
import sys
import urllib.request

port, token = sys.argv[1], sys.argv[2]
req = urllib.request.Request(
    f"http://127.0.0.1:{port}/api/config",
    headers={"X-Pikobs-Token": token},
)
try:
    with urllib.request.urlopen(req, timeout=0.6) as r:
        if r.status != 200:
            raise SystemExit(1)
        data = json.load(r)
        if not isinstance(data, dict) or "families" not in data:
            raise SystemExit(1)
        if str(data.get("session", {}).get("version", "")) != "17":
            raise SystemExit(1)
except Exception:
    raise SystemExit(1)
PY
}

server_healthy() {
    state_complete || return 1
    local pid
    pid="$(read_pid)" || return 1
    pid_is_managed "$pid" || return 1
    api_ready
}

clean_state() {
    rm -f "$PID_FILE" "$PORT_FILE" "$TOKEN_FILE" "$HOST_FILE"
}

stop_managed_process_if_needed() {
    local pid=""
    pid="$(read_pid 2>/dev/null || true)"
    [ -n "$pid" ] || return 0

    # Never kill a reused PID belonging to an unrelated command.
    if ! pid_is_managed "$pid"; then
        return 0
    fi

    kill "$pid" 2>/dev/null || true
    for _ in $(seq 1 40); do
        pid_alive "$pid" || return 0
        sleep .1
    done
    kill -9 "$pid" 2>/dev/null || true
}

choose_port() {
    "$PYTHON" - <<'PY'
import socket
s = socket.socket()
s.bind(('127.0.0.1', 0))
print(s.getsockname()[1])
s.close()
PY
}

new_token() {
    "$PYTHON" - <<'PY'
import secrets
print(secrets.token_urlsafe(32))
PY
}

ensure_server() {
    check_web_python

    if server_healthy; then
        return 0
    fi

    # State may refer to a dead/unhealthy Uvicorn. Stop it only when we can
    # prove it is one of our managed processes, then rebuild all state.
    stop_managed_process_if_needed
    clean_state

    local port token host pid
    port="$(choose_port)"
    token="$(new_token)"
    host="$(hostname -s)"

    printf '%s\n' "$port" > "$PORT_FILE"
    printf '%s\n' "$token" > "$TOKEN_FILE"
    printf '%s\n' "$host" > "$HOST_FILE"
    chmod 600 "$PORT_FILE" "$TOKEN_FILE" "$HOST_FILE"

    : > "$LOG_FILE"
    chmod 600 "$LOG_FILE"

    (
        # Keep the server CWD on a stable directory. The application code may
        # be upgraded/replaced under Downloads while the server is running;
        # using APP_DIR as the process CWD would then leave Uvicorn inside a
        # deleted directory and later qsub/bash calls would fail with getcwd.
        cd "$HOME"
        export PIKOBS_WEB_TOKEN="$token"
        export PIKOBS_WEB_PORT="$port"
        nohup "$PYTHON" -m uvicorn app.main:app \
            --app-dir "$APP_DIR" \
            --host 127.0.0.1 \
            --port "$port" \
            >>"$LOG_FILE" 2>&1 &
        echo $! > "$PID_FILE"
    )
    chmod 600 "$PID_FILE"
    pid="$(cat "$PID_FILE")"

    # Do not consider startup complete merely because the TCP port opened.
    # Require a real authenticated API response.
    for _ in $(seq 1 100); do
        if ! pid_alive "$pid"; then
            echo "ERROR: Pikobs Web stopped during startup." >&2
            tail -n 80 "$LOG_FILE" >&2 || true
            clean_state
            return 1
        fi
        if api_ready; then
            return 0
        fi
        sleep .1
    done

    echo "ERROR: Pikobs Web did not become API-ready." >&2
    tail -n 80 "$LOG_FILE" >&2 || true
    stop_managed_process_if_needed
    clean_state
    return 1
}

print_access() {
    local port token host pid
    port="$(cat "$PORT_FILE")"
    token="$(cat "$TOKEN_FILE")"
    host="$(cat "$HOST_FILE")"
    pid="$(cat "$PID_FILE")"
    cat <<MSG

Pikobs Web is running
  User : $USER
  Host : $host
  Port : $port
  PID  : $pid
  Log  : $LOG_FILE

From your workstation, create the SSH tunnel to THIS login host:
  ssh -4 -L ${port}:127.0.0.1:${port} ${USER}@${host}

Then open:
  http://127.0.0.1:${port}/#token=${token}

Commands:
  pikobs-web-server status
  pikobs-web-server stop
  pikobs-web-server restart
  pikobs-web-server log
MSG
}

start_server() {
    local was_healthy=0
    server_healthy && was_healthy=1 || true
    ensure_server
    if [ "$was_healthy" -eq 1 ]; then
        echo "Pikobs Web is already running and healthy."
    fi
    print_access
}

stop_server() {
    local pid=""
    pid="$(read_pid 2>/dev/null || true)"
    if [ -n "$pid" ] && pid_is_managed "$pid"; then
        stop_managed_process_if_needed
        echo "Pikobs Web stopped (PID $pid)."
    else
        echo "Pikobs Web is not running."
    fi
    clean_state
}

status_server() {
    if server_healthy; then
        print_access
        return 0
    fi

    local pid=""
    pid="$(read_pid 2>/dev/null || true)"
    if [ -n "$pid" ] && pid_is_managed "$pid"; then
        echo "Pikobs Web process exists but is not healthy (PID $pid)." >&2
    else
        echo "Pikobs Web is not running." >&2
    fi
    [ -f "$LOG_FILE" ] && echo "Last log: $LOG_FILE" >&2
    return 1
}

connection_info() {
    # Machine-readable command used by the workstation launcher. It also
    # self-heals stale state before returning connection information.
    ensure_server
    printf 'PIKOBS_WEB_CONNECTION\t%s\t%s\t%s\t%s\n' \
        "$(cat "$HOST_FILE")" \
        "$(cat "$PORT_FILE")" \
        "$(cat "$TOKEN_FILE")" \
        "$(cat "$PID_FILE")"
}

case "${1:-start}" in
    start) start_server ;;
    stop) stop_server ;;
    status) status_server ;;
    restart) stop_server; start_server ;;
    connection) connection_info ;;
    log)
        touch "$LOG_FILE"
        tail -f "$LOG_FILE"
        ;;
    *) echo "Usage: pikobs-web [start|stop|restart|status|log|connection]" >&2; exit 2 ;;
esac
