#!/bin/sh
set -eu

SCRIPT_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd)
PKG_DIR=$(dirname "$SCRIPT_DIR")
APP_ROOT=$(dirname "$PKG_DIR")
CONFIG_ENV="$PKG_DIR/app/.env"
RUNTIME_ENV="$PKG_DIR/app/environment"
BIN_DIR="$PKG_DIR/app/bin"
PROXYFLOWD="$BIN_DIR/proxyflowd"
MIHOMO="$BIN_DIR/mihomo-amd64"
DATA_DIR="$PKG_DIR/app/data"
CACHE_DIR="$PKG_DIR/app/cache"
APP_CONFIG_DIR="$APP_ROOT/config"
RUN_LOG="$PKG_DIR/log/run.log"
PF_PIDFILE="$CACHE_DIR/proxyflowd.pid"
MIHOMO_PIDFILE="$CACHE_DIR/mihomo.pid"
CONFIG_FILE="$DATA_DIR/mihomo.yaml"
STORE_FILE="$APP_CONFIG_DIR/proxyflow.json"

FWMARK="0x5af"
ROUTE_TABLE="105"
IPT="iptables -w"
CR=$(printf '\r')

log() { mkdir -p "$PKG_DIR/log"; printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$RUN_LOG"; }

load_env_file() {
  [ -f "$1" ] || return 0
  while IFS= read -r line || [ -n "$line" ]; do
    line=${line%"$CR"}
    case "$line" in ''|'#'*) continue ;; *=*) ;; *) continue ;; esac
    key=${line%%=*}; value=${line#*=}
    case "$key" in ''|*[!A-Za-z0-9_]*) continue ;; esac
    export "$key=$value"
  done < "$1"
}

pid_belongs_to() {
  pid=$1; bin=$2
  [ -d "/proc/$pid" ] || return 1
  exe=$(readlink -f "/proc/$pid/exe" 2>/dev/null || true)
  [ "$exe" = "$bin" ] && return 0
  cmd=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)
  case "$cmd" in "$bin "*|"$bin") return 0 ;; esac
  return 1
}

stop_pidfile() {
  pidfile=$1; bin=$2; name=$3
  [ -f "$pidfile" ] || return 0
  pid=$(cat "$pidfile" 2>/dev/null || true)
  if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && pid_belongs_to "$pid" "$bin"; then
    log "Stopping stale $name before restart, pid=$pid"
    kill "$pid" 2>/dev/null || true
    i=0; while kill -0 "$pid" 2>/dev/null; do i=$((i+1)); [ "$i" -ge 8 ] && { kill -9 "$pid" 2>/dev/null || true; break; }; sleep 1; done
  fi
  rm -f "$pidfile"
}

teardown_tproxy() {
  $IPT -t mangle -D PREROUTING -j PROXYFLOW 2>/dev/null || true
  $IPT -t mangle -F PROXYFLOW 2>/dev/null || true
  $IPT -t mangle -X PROXYFLOW 2>/dev/null || true
  $IPT -t nat -D PREROUTING -j PROXYFLOW_DNS 2>/dev/null || true
  $IPT -t nat -F PROXYFLOW_DNS 2>/dev/null || true
  $IPT -t nat -X PROXYFLOW_DNS 2>/dev/null || true
  ip rule del fwmark ${FWMARK} table ${ROUTE_TABLE} pref 9010 2>/dev/null || true
  ip route flush table ${ROUTE_TABLE} 2>/dev/null || true
}

api_get_sources() {
  url="http://127.0.0.1:${APP_PORT_WEB}/api/v1/firewall-sources"
  if command -v curl >/dev/null 2>&1; then
    if [ -n "${PROXYFLOW_SECRET:-}" ]; then
      curl -fs -m 3 -H "Authorization: Bearer ${PROXYFLOW_SECRET}" "$url" 2>/dev/null || true
    else
      curl -fs -m 3 "$url" 2>/dev/null || true
    fi
  else
    wget -q -T 3 -O - "$url" 2>/dev/null || true
  fi
}

extract_sources() {
  api_get_sources | tr '"[]{}:,' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/32$' || true
}

setup_tproxy() {
  sources=$(extract_sources || true)
  if [ -z "$sources" ]; then
    log "No enabled device rules; skip TPROXY (fail-open)."
    return 0
  fi
  modprobe xt_TPROXY >/dev/null 2>&1 || true
  ip rule add fwmark ${FWMARK} table ${ROUTE_TABLE} pref 9010 2>/dev/null || true
  ip route add local default dev lo table ${ROUTE_TABLE} 2>/dev/null || true

  $IPT -t nat -N PROXYFLOW_DNS 2>/dev/null || $IPT -t nat -F PROXYFLOW_DNS
  $IPT -t nat -A PROXYFLOW_DNS -i lo -j RETURN
  printf '%s\n' "$sources" | while IFS= read -r cidr || [ -n "$cidr" ]; do
    [ -n "$cidr" ] || continue
    $IPT -t nat -A PROXYFLOW_DNS -s "$cidr" -p udp --dport 53 -j REDIRECT --to-ports "$PROXYFLOW_DNS_PORT"
    $IPT -t nat -A PROXYFLOW_DNS -s "$cidr" -p tcp --dport 53 -j REDIRECT --to-ports "$PROXYFLOW_DNS_PORT"
  done
  $IPT -t nat -A PREROUTING -j PROXYFLOW_DNS

  $IPT -t mangle -N PROXYFLOW 2>/dev/null || $IPT -t mangle -F PROXYFLOW
  $IPT -t mangle -A PROXYFLOW -i lo -j RETURN
  $IPT -t mangle -A PROXYFLOW -p udp --dport 53 -j RETURN
  $IPT -t mangle -A PROXYFLOW -p tcp --dport 53 -j RETURN
  for cidr in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 0.0.0.0/8 127.0.0.0/8 169.254.0.0/16 224.0.0.0/4 240.0.0.0/4 255.255.255.255/32; do
    $IPT -t mangle -A PROXYFLOW -d "$cidr" -j RETURN
  done
  OLD_IFS=$IFS; IFS=','
  for cidr in ${PROXYFLOW_BYPASS_CIDR:-}; do [ -n "$cidr" ] && $IPT -t mangle -A PROXYFLOW -d "$cidr" -j RETURN || true; done
  IFS=$OLD_IFS
  $IPT -t mangle -A PROXYFLOW -p tcp -m multiport --dports "${PROXYFLOW_MIXED_PORT},${PROXYFLOW_TPROXY_PORT},${PROXYFLOW_CONTROLLER_PORT},${APP_PORT_WEB}" -j RETURN
  printf '%s\n' "$sources" | while IFS= read -r cidr || [ -n "$cidr" ]; do
    [ -n "$cidr" ] || continue
    $IPT -t mangle -A PROXYFLOW -s "$cidr" -p tcp -j TPROXY --on-port "$PROXYFLOW_TPROXY_PORT" --tproxy-mark ${FWMARK}
    $IPT -t mangle -A PROXYFLOW -s "$cidr" -p udp -j TPROXY --on-port "$PROXYFLOW_TPROXY_PORT" --tproxy-mark ${FWMARK}
    log "Added device TPROXY rule: $cidr"
  done
  $IPT -t mangle -A PREROUTING -j PROXYFLOW
  log "PROXYFLOW TPROXY installed (fwmark=${FWMARK}, table=${ROUTE_TABLE})."
}

mkdir -p "$DATA_DIR" "$CACHE_DIR" "$APP_CONFIG_DIR" "$PKG_DIR/log"
for f in GeoSite.dat GeoIP.dat Country.mmdb; do
  [ -s "$DATA_DIR/$f" ] || [ ! -s "$PKG_DIR/app/geodata/$f" ] || cp "$PKG_DIR/app/geodata/$f" "$DATA_DIR/$f" 2>/dev/null || true
done
chmod 755 "$PROXYFLOWD" "$MIHOMO" 2>/dev/null || true
load_env_file "$CONFIG_ENV"
load_env_file "$RUNTIME_ENV"
: "${APP_PORT_WEB:=28081}"
: "${PROXYFLOW_WEB_USER:=admin}"
: "${PROXYFLOW_WEB_PASSWORD:=ikuai8.com}"
: "${PROXYFLOW_CONTROLLER_SECRET:=proxyflow-internal}"
: "${PROXYFLOW_SECRET:=}"
: "${PROXYFLOW_TPROXY_ENABLE:=1}"
: "${PROXYFLOW_MIXED_PORT:=27891}"
: "${PROXYFLOW_TPROXY_PORT:=27893}"
: "${PROXYFLOW_DNS_PORT:=21053}"
: "${PROXYFLOW_CONTROLLER_PORT:=29091}"
: "${PROXYFLOW_DNS_MODE:=1}"
: "${PROXYFLOW_BYPASS_CIDR:=192.168.0.0/16,10.0.0.0/8,172.16.0.0/12}"

arch=$(uname -m 2>/dev/null || echo unknown)
case "$arch" in x86_64|amd64) ;; *) log "Unsupported architecture: $arch (amd64-only)"; exit 1 ;; esac
[ -x "$PROXYFLOWD" ] || { log "proxyflowd missing: $PROXYFLOWD"; exit 1; }
[ -x "$MIHOMO" ] || { log "mihomo missing: $MIHOMO"; exit 1; }

teardown_tproxy
stop_pidfile "$PF_PIDFILE" "$PROXYFLOWD" proxyflowd
stop_pidfile "$MIHOMO_PIDFILE" "$MIHOMO" mihomo

set -- "$PROXYFLOWD" -listen "0.0.0.0:${APP_PORT_WEB}" -data "$STORE_FILE" -config-out "$CONFIG_FILE" -web-user "$PROXYFLOW_WEB_USER" -web-password "$PROXYFLOW_WEB_PASSWORD" -dashboard-dir "$PKG_DIR/app/zashboard" -controller-secret "$PROXYFLOW_CONTROLLER_SECRET" -secret "$PROXYFLOW_SECRET" -log "$RUN_LOG" -mixed-port "$PROXYFLOW_MIXED_PORT" -tproxy-port "$PROXYFLOW_TPROXY_PORT" -dns-port "$PROXYFLOW_DNS_PORT" -controller-port "$PROXYFLOW_CONTROLLER_PORT" -dns-mode "$PROXYFLOW_DNS_MODE" -bypass-cidr "$PROXYFLOW_BYPASS_CIDR"
log "Starting proxyflowd on :${APP_PORT_WEB}"
"$@" >> "$RUN_LOG" 2>&1 &
PF_PID=$!
printf '%s\n' "$PF_PID" > "$PF_PIDFILE"
sleep 1
kill -0 "$PF_PID" 2>/dev/null || { rm -f "$PF_PIDFILE"; log "proxyflowd failed to start"; exit 1; }

# Wait until proxyflowd generates Mihomo config.
i=0
while [ "$i" -lt 20 ]; do [ -s "$CONFIG_FILE" ] && break; i=$((i+1)); sleep 1; done
[ -s "$CONFIG_FILE" ] || { log "Mihomo config not generated: $CONFIG_FILE"; exit 1; }

set -- "$MIHOMO" -d "$DATA_DIR" -f "$CONFIG_FILE"
log "Starting mihomo data plane"
"$@" >> "$RUN_LOG" 2>&1 &
MI_PID=$!
printf '%s\n' "$MI_PID" > "$MIHOMO_PIDFILE"
sleep 2
kill -0 "$MI_PID" 2>/dev/null || { rm -f "$MIHOMO_PIDFILE"; log "mihomo failed to start"; exit 1; }

if [ "$PROXYFLOW_TPROXY_ENABLE" = "1" ]; then
  setup_tproxy || teardown_tproxy
else
  log "TPROXY disabled; Web/API and manual proxy ports only."
fi

(
  while kill -0 "$PF_PID" 2>/dev/null && kill -0 "$MI_PID" 2>/dev/null; do sleep 5; done
  teardown_tproxy
  rm -f "$PF_PIDFILE" "$MIHOMO_PIDFILE"
  log "proxyflow component exited; firewall rules removed by watchdog."
) >/dev/null 2>&1 &

log "proxyflow started. Web: http://<router-ip>:${APP_PORT_WEB}/"
exit 0


