#!/bin/sh
# Alpine 5G Router – connection script (run by 5g-router service or manually)
# Uses /etc/5g-router.conf if present. Supports watchdog and failover.
# Rev: 2  (see REVISION in repo root)

set -e

CONFIG="/etc/5g-router.conf"
LOG_FILE="/var/log/5g-router.log"

# Defaults
AT_PORT="/dev/ttyUSB1"
APN="internet"
WAN_IF="eth1"
LAN_IF="eth0.100"
FAILOVER_ENABLED="no"
FAILOVER_IF="eth0"
FAILOVER_METRIC="202"
WATCHDOG_INTERVAL="0"
LOG_SIGNAL="yes"
WEAK_SIGNAL_CSQ="10"

[ -f "$CONFIG" ] && . "$CONFIG" || true

# Lock file for exclusive AT port access (shared with modem-status-at.sh)
AT_LOCK="/var/lock/5g-at.lock"
exec 9>"$AT_LOCK"
flock -n 9 || { echo "Another process is using the AT port, waiting..." >&2; flock 9; }

# Log to file; also to stdout only when running in a terminal (avoids duplicate lines when service redirects stdout to log)
log() {
    _m="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
    echo "$_m" >> "$LOG_FILE" 2>/dev/null || true
    [ -t 1 ] && echo "$_m" || true
}

# Fix broken /dev/ttyUSB* node (if it is a regular file instead of char device - e.g. after modem reconnect)
# Also remove stray /dev/ttyUSB (no digit) which can appear and steal the name from real devices
fix_ttyusb_if_needed() {
    # Remove stray /dev/ttyUSB (no number) if it's a regular file
    if [ -e /dev/ttyUSB ] && [ -f /dev/ttyUSB ] && [ ! -c /dev/ttyUSB ]; then
        rm -f /dev/ttyUSB
    fi
    case "$AT_PORT" in
        /dev/ttyUSB[0-9]*) ;;
        *) return 0 ;;
    esac
    [ ! -e "$AT_PORT" ] && return 0 || true
    if [ -f "$AT_PORT" ] && [ ! -c "$AT_PORT" ]; then
        _num="${AT_PORT#/dev/ttyUSB}"
        log "Fixing $AT_PORT (was regular file, recreating as char device)"
        rm -f "$AT_PORT"
        mknod "$AT_PORT" c 188 "$_num"
        chmod 660 "$AT_PORT"
        chown root:dialout "$AT_PORT" 2>/dev/null || true
    fi
}

# Never write to AT_PORT unless it is a character device; otherwise a redirect would create a regular file and break the port
get_at_response() {
    _cmd="$1"
    _wait="${2:-2}"
    timeout $((_wait + 4)) sh -c "
        [ -c \"$AT_PORT\" ] || exit 1
        cat $AT_PORT 2>/dev/null &
        _pid=\$!
        sleep 0.5
        echo \"${_cmd}\r\" > $AT_PORT
        sleep $_wait
        kill \$_pid 2>/dev/null
    " 2>&1
}

wait_for_modem() {
    log "Waiting for modem AT port..."
    for _i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
        fix_ttyusb_if_needed
        if [ -c "$AT_PORT" ]; then
            log "Modem AT port available"
            return 0
        fi
        sleep 2
    done
    log "Modem AT port not available"
    return 1
}

check_usb_mode() {
    if lsusb 2>/dev/null | grep -q "0e8d:7127"; then
        log "WARN: Modem is in Mode 41 (7127). AT commands may not work. Reboot or power-cycle modem."
        return 1
    fi
    return 0
}

do_connect() {
    # eth1 (RNDIS) does NOT provide DHCP. We get IP (and DNS) only via AT commands
    # and configure the interface manually. Do not run dhclient on eth1.
    check_usb_mode || return 1
    # Modem can take 5–15s to respond after port appears at boot; retry initial AT up to 3 times
    _at_ok=0
    for _try in 1 2 3; do
        if get_at_response "AT" 5 | grep -q "OK"; then
            _at_ok=1
            break
        fi
        if [ $_try -lt 3 ]; then
            log "AT not OK, retry $_try/3 in 5s..."
            sleep 5
        fi
    done
    if [ $_at_ok -eq 0 ]; then
        log "AT not OK (try longer wait or different AT port)"
        return 1
    fi

    # Signal (optional log)
    _resp=$(get_at_response "AT+CSQ" 1)
    _csq=$(echo "$_resp" | grep "+CSQ:" | grep -oE '[0-9]+' | head -1)
    [ -n "$_csq" ] && [ "$LOG_SIGNAL" = "yes" ] && echo "$(date -Iseconds) CSQ=$_csq" >> "$LOG_FILE" 2>/dev/null || true
    [ -n "$_csq" ] && [ -n "$WEAK_SIGNAL_CSQ" ] && [ "$WEAK_SIGNAL_CSQ" -gt 0 ] && [ "$_csq" -lt "$WEAK_SIGNAL_CSQ" ] && log "WARN weak signal CSQ=$_csq" || true

    log "Configuring APN: $APN"
    get_at_response "AT+CGDCONT=1,\"IP\",\"$APN\"" 2 | grep -q "OK" || true
    log "Activating PDP context..."
    get_at_response "AT+CGACT=1,1" 3 | grep -qE "(OK|CGEV)" || true

    _ip=""
    for _retry in 1 2 3 4 5; do
        _resp=$(get_at_response "AT+CGPADDR=1" 2)
        _ip=$(echo "$_resp" | grep "+CGPADDR:" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1)
        if [ -n "$_ip" ]; then break; fi
        if [ $_retry -lt 5 ]; then
            log "No IP yet, retry $_retry/5 in 3s..."
            sleep 3
        fi
    done
    if [ -z "$_ip" ]; then
        log "Could not get modem IP (check signal/registration: AT+CSQ, AT+CEREG?)"
        return 1
    fi

    # Get DNS from modem (AT+CGCONTRDP=1); modem does not provide DHCP, only IP/DNS via AT
    _dns1=""; _dns2=""
    _contrdp=$(get_at_response "AT+CGCONTRDP=1" 2)
    _allips=""
    for _a in $(echo "$_contrdp" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'); do
        [ "$_a" = "$_ip" ] && continue || true
        _allips="${_allips} ${_a}"
    done
    for _a in $_allips; do
        [ -z "$_a" ] && continue || true
        _dns2="$_dns1"
        _dns1="$_a"
    done
    [ -z "$_dns1" ] && [ -n "$DNS_SERVERS" ] && _dns1=$(echo "$DNS_SERVERS" | cut -d',' -f1 | tr -d ' ') && _dns2=$(echo "$DNS_SERVERS" | cut -d',' -f2 | tr -d ' ')
    if [ -n "$_dns1" ]; then
        : > /etc/resolv.conf
        echo "nameserver $_dns1" >> /etc/resolv.conf
        [ -n "$_dns2" ] && echo "nameserver $_dns2" >> /etc/resolv.conf || true
        log "DNS set (from modem or config)"
    fi

    log "Configuring $WAN_IF with IP $_ip (no DHCP - from AT+CGPADDR=1)"
    ip link set "$WAN_IF" up 2>/dev/null || true
    ip addr flush dev "$WAN_IF" 2>/dev/null || true
    ip addr add "$_ip/32" dev "$WAN_IF"
    ip route add default dev "$WAN_IF" metric 50 2>/dev/null || true
    log "Interface configured"

    log "Setting up NAT..."
    echo 1 > /proc/sys/net/ipv4/ip_forward
    iptables -t nat -C POSTROUTING -o "$WAN_IF" -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o "$WAN_IF" -j MASQUERADE
    iptables -C FORWARD -i "$LAN_IF" -o "$WAN_IF" -j ACCEPT 2>/dev/null || iptables -A FORWARD -i "$LAN_IF" -o "$WAN_IF" -j ACCEPT
    iptables -C FORWARD -i "$WAN_IF" -o "$LAN_IF" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || iptables -A FORWARD -i "$WAN_IF" -o "$LAN_IF" -m state --state RELATED,ESTABLISHED -j ACCEPT
    log "NAT configured"
    return 0
}

do_failover() {
    [ "$FAILOVER_ENABLED" != "yes" ] && return 0 || true
    # Ensure there is a default route via failover interface (higher metric so 5G wins when up)
    ip route add default dev "$FAILOVER_IF" metric "$FAILOVER_METRIC" 2>/dev/null || true
}

# Main
run_once() {
    wait_for_modem || { do_failover; return 1; }
    if do_connect; then
        log "5G connection successful!"
        return 0
    else
        do_failover
        return 1
    fi
}

if [ -n "$WATCHDOG_INTERVAL" ] && [ "$WATCHDOG_INTERVAL" -gt 0 ]; then
    log "Starting 5G connection (watchdog every ${WATCHDOG_INTERVAL}s)..."
    while true; do
        run_once || true
        sleep "$WATCHDOG_INTERVAL"
    done
else
    log "Starting 5G connection..."
    run_once || true
    log "Holding process for service (stop with: service 5g-router stop)."
    exec sleep infinity
fi
