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

CONFIG_ROOT="${XDG_CONFIG_HOME:-$HOME/.config}/ssh-http-proxy"
BIN_DIR="$HOME/.local/bin"
SSH_CONFIG="$HOME/.ssh/config"
HELPER="$BIN_DIR/ssh-http-connect.py"

if [ -t 1 ]; then
  RESET='\033[0m'; BOLD='\033[1m'; CYAN='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'
else
  RESET=''; BOLD=''; CYAN=''; GREEN=''; YELLOW=''; RED=''
fi
say() { printf '%b\n' "$*"; }
die() { say "${RED}Error:${RESET} $*" >&2; exit 1; }
ask() { prompt="$1"; default="${2:-}"; if [ -n "$default" ]; then printf '%b' "$prompt [$default]: " >&2; else printf '%b' "$prompt: " >&2; fi; read -r answer; printf '%s' "${answer:-$default}"; }
usage() {
  cat <<'EOF'
Usage: ./install-ssh-http-proxy.sh [options]

Options:
  --alias NAME              SSH alias, default: customer-jump
  --ssh-host HOST           SSH server reachable through HTTP CONNECT
  --ssh-port PORT           SSH port, default: 22
  --ssh-user USER           SSH username
  --proxy-host HOST         HTTP proxy hostname or IP
  --proxy-port PORT         HTTP proxy port, default: 8080
  --proxy-user USER         Optional HTTP Basic proxy username
  --proxy-password PASS     Optional HTTP Basic proxy password
  --identity-file PATH      Optional SSH private key path
  --non-interactive         Fail if required options are missing
  -h, --help                Show this help
EOF
}

ALIAS="customer-jump"; SSH_HOST=""; SSH_PORT="22"; SSH_USER=""; PROXY_HOST=""; PROXY_PORT="8080"; PROXY_USER=""; PROXY_PASSWORD=""; IDENTITY_FILE=""; NON_INTERACTIVE=0
while [ "$#" -gt 0 ]; do
  case "$1" in
    --alias) ALIAS="$2"; shift 2 ;;
    --ssh-host) SSH_HOST="$2"; shift 2 ;;
    --ssh-port) SSH_PORT="$2"; shift 2 ;;
    --ssh-user) SSH_USER="$2"; shift 2 ;;
    --proxy-host) PROXY_HOST="$2"; shift 2 ;;
    --proxy-port) PROXY_PORT="$2"; shift 2 ;;
    --proxy-user) PROXY_USER="$2"; shift 2 ;;
    --proxy-password) PROXY_PASSWORD="$2"; shift 2 ;;
    --identity-file) IDENTITY_FILE="$2"; shift 2 ;;
    --non-interactive) NON_INTERACTIVE=1; shift ;;
    -h|--help) usage; exit 0 ;;
    *) die "Unknown option: $1" ;;
  esac
done

say "${CYAN}${BOLD}+----------------------------------------------------------+${RESET}"
say "${CYAN}${BOLD}|            SSH TUNNEL THROUGH HTTP CONNECT PROXY          |${RESET}"
say "${CYAN}${BOLD}+----------------------------------------------------------+${RESET}"
printf '\n'

if [ "$NON_INTERACTIVE" -eq 0 ]; then
  ALIAS="$(ask 'SSH alias' "$ALIAS")"
  SSH_HOST="$(ask 'SSH server host' "$SSH_HOST")"
  SSH_PORT="$(ask 'SSH server port' "$SSH_PORT")"
  SSH_USER="$(ask 'SSH username' "$SSH_USER")"
  PROXY_HOST="$(ask 'HTTP proxy host' "$PROXY_HOST")"
  PROXY_PORT="$(ask 'HTTP proxy port' "$PROXY_PORT")"
  PROXY_USER="$(ask 'Proxy username, leave empty if unused' "$PROXY_USER")"
  if [ -n "$PROXY_USER" ] && [ -z "$PROXY_PASSWORD" ]; then
    printf 'Proxy password: '; read -r -s PROXY_PASSWORD; printf '\n'
  fi
  IDENTITY_FILE="$(ask 'SSH identity file, leave empty for SSH default' "$IDENTITY_FILE")"
fi

command -v python3 >/dev/null 2>&1 || die 'python3 is required.'
command -v ssh >/dev/null 2>&1 || die 'OpenSSH client is required.'
case "$ALIAS" in ''|*[!A-Za-z0-9_.-]*) die 'Alias may only contain letters, digits, dot, underscore and dash.' ;; esac
case "$SSH_PORT:$PROXY_PORT" in *[!0-9:]*) die 'Ports must be numeric.' ;; esac
[ -n "$SSH_HOST" ] || die 'Missing --ssh-host.'
[ -n "$SSH_USER" ] || die 'Missing --ssh-user.'
[ -n "$PROXY_HOST" ] || die 'Missing --proxy-host.'

mkdir -p "$CONFIG_ROOT" "$BIN_DIR" "$HOME/.ssh"
chmod 700 "$CONFIG_ROOT" "$BIN_DIR" "$HOME/.ssh"

cat > "$HELPER" <<'PYTHON'
#!/usr/bin/env python3
import argparse
import base64
import os
import socket
import sys


def read_config(path):
    config = {}
    with open(path, encoding="utf-8") as handle:
        for raw_line in handle:
            line = raw_line.rstrip("\n")
            if not line or line.startswith("#") or "=" not in line:
                continue
            key, value = line.split("=", 1)
            config[key] = value
    return config


def fail(message):
    print("ssh-http-connect: " + message, file=sys.stderr)
    raise SystemExit(1)


parser = argparse.ArgumentParser()
parser.add_argument("--config", required=True)
parser.add_argument("target_host")
parser.add_argument("target_port", type=int)
args = parser.parse_args()
config = read_config(os.path.expanduser(args.config))
proxy_host = config.get("PROXY_HOST", "")
proxy_port = int(config.get("PROXY_PORT", "8080"))
if not proxy_host:
    fail("missing PROXY_HOST")

sock = socket.create_connection((proxy_host, proxy_port), timeout=15)
request = [
    f"CONNECT {args.target_host}:{args.target_port} HTTP/1.1",
    f"Host: {args.target_host}:{args.target_port}",
    "Proxy-Connection: Keep-Alive",
]
proxy_user = config.get("PROXY_USER", "")
if proxy_user:
    token = base64.b64encode((proxy_user + ":" + config.get("PROXY_PASSWORD", "")).encode()).decode()
    request.append("Proxy-Authorization: Basic " + token)
sock.sendall(("\r\n".join(request) + "\r\n\r\n").encode())
response = b""
while b"\r\n\r\n" not in response:
    chunk = sock.recv(4096)
    if not chunk:
        fail("proxy closed the connection")
    response += chunk
    if len(response) > 65536:
        fail("proxy response header is too large")
status_line = response.split(b"\r\n", 1)[0].decode("latin-1", "replace")
parts = status_line.split(" ", 2)
if len(parts) < 2 or parts[1] != "200":
    fail("HTTP CONNECT failed: " + status_line)

extra = response.split(b"\r\n\r\n", 1)[1]
if extra:
    os.write(sys.stdout.fileno(), extra)
sock.settimeout(None)
stdin_open = True
while True:
    inputs = [sock]
    if stdin_open:
        inputs.append(sys.stdin.buffer)
    readable, _, _ = __import__("select").select(inputs, [], [])
    if sock in readable:
        data = sock.recv(65536)
        if not data:
            break
        os.write(sys.stdout.fileno(), data)
    if sys.stdin.buffer in readable:
        data = os.read(sys.stdin.fileno(), 65536)
        if not data:
            stdin_open = False
            try:
                sock.shutdown(socket.SHUT_WR)
            except OSError:
                pass
        else:
            sock.sendall(data)
PYTHON
chmod 700 "$HELPER"

PROFILE="$CONFIG_ROOT/$ALIAS.conf"
umask 077
{
  printf 'PROXY_HOST=%s\n' "$PROXY_HOST"
  printf 'PROXY_PORT=%s\n' "$PROXY_PORT"
  printf 'PROXY_USER=%s\n' "$PROXY_USER"
  printf 'PROXY_PASSWORD=%s\n' "$PROXY_PASSWORD"
} > "$PROFILE"
chmod 600 "$PROFILE"

touch "$SSH_CONFIG"
chmod 600 "$SSH_CONFIG"
START="# >>> ssh-http-proxy:$ALIAS >>>"
END="# <<< ssh-http-proxy:$ALIAS <<<"
TMP_CONFIG="$SSH_CONFIG.tmp.$$"
awk -v start="$START" -v end="$END" '
  $0 == start { skip=1; next }
  $0 == end { skip=0; next }
  !skip { print }
' "$SSH_CONFIG" > "$TMP_CONFIG"
{
  cat "$TMP_CONFIG"
  printf '\n%s\n' "$START"
  printf 'Host %s\n' "$ALIAS"
  printf '  HostName %s\n' "$SSH_HOST"
  printf '  Port %s\n' "$SSH_PORT"
  printf '  User %s\n' "$SSH_USER"
  printf '  ProxyCommand python3 %s --config %s %%h %%p\n' "$HELPER" "$PROFILE"
  printf '  ServerAliveInterval 30\n'
  printf '  ServerAliveCountMax 3\n'
  [ -z "$IDENTITY_FILE" ] || printf '  IdentityFile %s\n' "$IDENTITY_FILE"
  printf '%s\n' "$END"
} > "$SSH_CONFIG"
rm -f "$TMP_CONFIG"
chmod 600 "$SSH_CONFIG"

python3 -m py_compile "$HELPER"
printf '\n'
say "${GREEN}${BOLD}Installed successfully.${RESET}"
say "Config file : $PROFILE"
say "SSH config  : $SSH_CONFIG"
printf '\n'
say "${BOLD}Connect to SSH:${RESET}"
say "  ssh $ALIAS"
printf '\n'
say "${BOLD}Create a local SOCKS5 tunnel on port 1080:${RESET}"
say "  ssh -N -D 127.0.0.1:1080 $ALIAS"
printf '\n'
say "${BOLD}Forward local port 8080 to an internal service:${RESET}"
say "  ssh -N -L 127.0.0.1:8080:internal-host:80 $ALIAS"
printf '\n'
say "${YELLOW}The HTTP proxy must allow CONNECT to $SSH_HOST:$SSH_PORT.${RESET}"
