#!/bin/sh
set -eu

LISTEN_PORT=19092
UPSTREAM_HOST=127.0.0.1
UPSTREAM_PORT=8080
CLIENT_BASE_PATH=/
UPSTREAM_BASE_PATH=/
TARGET=/etc/nginx/conf.d/keycloak-ssh-loopback.conf
APPLY=0

usage() {
    cat <<'EOF'
Render a loopback-only Nginx listener for a Keycloak SSH tunnel.

Default behavior prints the configuration and changes nothing.

Usage:
  sh configure-keycloak-nginx-loopback.sh [options]

Options:
  --listen-port PORT         Loopback Nginx port (default: 19092)
  --upstream-host HOST       Keycloak host from Nginx (default: 127.0.0.1)
  --upstream-port PORT       Keycloak upstream port (default: 8080)
  --base-path PATH           Path entered in the app (default: /)
  --upstream-base-path PATH  Path served by Keycloak (default: /)
  --target FILE              Nginx file used with --apply
  --apply                    Install, validate, and reload Nginx
  --help                     Show this help

Example for Keycloak already serving /id on port 9092:
  sh configure-keycloak-nginx-loopback.sh \
    --listen-port 19092 --upstream-port 9092 \
    --base-path /id --upstream-base-path /id
EOF
}

require_value() {
    if [ "$#" -lt 2 ] || [ -z "$2" ]; then
        printf 'Missing value for %s\n' "$1" >&2
        exit 2
    fi
}

while [ "$#" -gt 0 ]; do
    case "$1" in
        --listen-port) require_value "$@"; LISTEN_PORT=$2; shift 2 ;;
        --upstream-host) require_value "$@"; UPSTREAM_HOST=$2; shift 2 ;;
        --upstream-port) require_value "$@"; UPSTREAM_PORT=$2; shift 2 ;;
        --base-path) require_value "$@"; CLIENT_BASE_PATH=$2; shift 2 ;;
        --upstream-base-path) require_value "$@"; UPSTREAM_BASE_PATH=$2; shift 2 ;;
        --target) require_value "$@"; TARGET=$2; shift 2 ;;
        --apply) APPLY=1; shift ;;
        --help|-h) usage; exit 0 ;;
        *) printf 'Unknown option: %s\n' "$1" >&2; usage >&2; exit 2 ;;
    esac
done

validate_port() {
    case "$1" in ''|*[!0-9]*) return 1 ;; esac
    [ "$1" -ge 1 ] && [ "$1" -le 65535 ]
}

validate_path() {
    case "$1" in /*) ;; *) return 1 ;; esac
    case "$1" in *[!A-Za-z0-9._~/-]*) return 1 ;; esac
}

normalize_path() {
    value=$1
    while [ "$value" != "/" ] && [ "${value%/}" != "$value" ]; do value=${value%/}; done
    printf '%s' "$value"
}

if ! validate_port "$LISTEN_PORT" || ! validate_port "$UPSTREAM_PORT"; then
    printf 'Ports must be numbers from 1 through 65535.\n' >&2; exit 2
fi
case "$UPSTREAM_HOST" in ''|*[!A-Za-z0-9._-]*) printf 'Invalid upstream host.\n' >&2; exit 2 ;; esac
if ! validate_path "$CLIENT_BASE_PATH" || ! validate_path "$UPSTREAM_BASE_PATH"; then
    printf 'Base paths must begin with / and contain only URL path characters.\n' >&2; exit 2
fi

CLIENT_BASE_PATH=$(normalize_path "$CLIENT_BASE_PATH")
UPSTREAM_BASE_PATH=$(normalize_path "$UPSTREAM_BASE_PATH")
if [ "$CLIENT_BASE_PATH" = "/" ]; then
    CLIENT_LOCATION=/; BASE_REDIRECT_BLOCK=
else
    CLIENT_LOCATION=$CLIENT_BASE_PATH/
    BASE_REDIRECT_BLOCK="location = $CLIENT_BASE_PATH { return 308 $CLIENT_BASE_PATH/; }"
fi
if [ "$UPSTREAM_BASE_PATH" = "/" ]; then UPSTREAM_LOCATION=/; else UPSTREAM_LOCATION=$UPSTREAM_BASE_PATH/; fi

render_config() {
    cat <<EOF
# Generated loopback-only Keycloak proxy. Review before installation.
server {
    listen 127.0.0.1:$LISTEN_PORT;
    listen [::1]:$LISTEN_PORT;
    server_name localhost;
    server_tokens off;
    allow 127.0.0.1;
    allow ::1;
    deny all;
    location = /asl-keycloak-proxy-health { access_log off; return 204; }
    $BASE_REDIRECT_BLOCK
    location ^~ $CLIENT_LOCATION {
        proxy_pass http://$UPSTREAM_HOST:$UPSTREAM_PORT$UPSTREAM_LOCATION;
        proxy_http_version 1.1;
        proxy_set_header Host \$http_host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$remote_addr;
        proxy_set_header X-Forwarded-Host \$http_host;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_set_header X-Forwarded-Prefix $CLIENT_BASE_PATH;
        proxy_set_header Connection "";
        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        proxy_redirect off;
    }
}
EOF
}

if [ "$APPLY" -eq 0 ]; then render_config; exit 0; fi
if [ "$(id -u)" -ne 0 ]; then printf '%s\n' '--apply requires root. Render without --apply for review first.' >&2; exit 1; fi
if ! command -v nginx >/dev/null 2>&1; then printf 'Nginx is not installed or is not in PATH.\n' >&2; exit 1; fi
target_dir=$(dirname "$TARGET")
if [ ! -d "$target_dir" ]; then printf 'Target directory does not exist: %s\n' "$target_dir" >&2; exit 1; fi

candidate=$(mktemp); backup=$(mktemp); had_existing=0
cleanup() { rm -f "$candidate" "$backup"; }
trap cleanup EXIT HUP INT TERM
render_config > "$candidate"
if [ -f "$TARGET" ]; then cp -p "$TARGET" "$backup"; had_existing=1; fi
install -m 0644 "$candidate" "$TARGET"
restore_target() { if [ "$had_existing" -eq 1 ]; then cp -p "$backup" "$TARGET"; else rm -f "$TARGET"; fi; }
if ! nginx -t; then restore_target; printf 'Nginx validation failed; the previous configuration was restored.\n' >&2; exit 1; fi
reload_nginx() { if command -v systemctl >/dev/null 2>&1; then systemctl reload nginx; else nginx -s reload; fi; }
if ! reload_nginx; then
    restore_target; nginx -t >/dev/null 2>&1 || true; reload_nginx >/dev/null 2>&1 || true
    printf 'Nginx reload failed; the previous configuration was restored.\n' >&2; exit 1
fi
printf 'Installed %s with loopback listener 127.0.0.1:%s.\n' "$TARGET" "$LISTEN_PORT"
printf 'In the app, use Keycloak port %s and base path %s.\n' "$LISTEN_PORT" "$CLIENT_BASE_PATH"
