#!/bin/bash

# Cistech VPN Connection Script with OpenConnect-SSO
# Usage: ./openconnect-vpn [-c|--connect] [-d|--disconnect] [-m|--menu] [-r|--routes] [-s|--status] [--help]
#
# Options:
#   -c, --connect     Connect to VPN and exit
#   -d, --disconnect  Disconnect VPN and exit
#   -m, --menu        Skip auto-connect, show menu directly
#   -r, --routes      Show routing table and exit
#   -s, --status      Show VPN status and exit
#   --help            Show this help message

# Credentials from environment variables (set by runtipi)
VPN_EMAIL="${VPN_EMAIL:-}"
VPN_PASSWORD="${VPN_PASSWORD:-}"
VPN_TOTP_SECRET="${VPN_TOTP_SECRET:-}"
VPN_HOST="${VPN_HOST:-}"
TARGET_IP="${TARGET_IP:-10.3.1.201}"
TARGET_SUBNET="$(echo "$TARGET_IP" | cut -d. -f1-3).0/24"
VPN_INTERFACE="${VPN_INTERFACE:-tun0}"
CONTAINER_NETWORK="172.30.0.0/24"


# Log directory
LOG_DIR="/var/log/openconnect-vpn"
LOG_RETENTION_DAYS=7
mkdir -p "$LOG_DIR" 2>/dev/null

get_log_file() {
    echo "$LOG_DIR/$(date '+%Y-%m-%d').log"
}

cleanup_old_logs() {
    find "$LOG_DIR" -name "*.log" -type f -mtime +$LOG_RETENTION_DAYS -delete 2>/dev/null
}

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
GRAY='\033[0;90m'
NC='\033[0m'

print_banner() {
    echo -e "${CYAN}========================================${NC}"
    echo -e "${CYAN}    Cistech VPN Connection Script       ${NC}"
    echo -e "${CYAN}========================================${NC}"
    echo ""
}

# Flags
SKIP_AUTO_CONNECT=false
DO_CONNECT=false
DO_DISCONNECT=false

# Logging
log() {
    local level="$1"
    local msg="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local timestamp_short=$(date '+%H:%M:%S')
    local log_file=$(get_log_file)

    local msg_plain=$(echo -e "$msg" | sed 's/\x1b\[[0-9;]*m//g')
    echo "[$timestamp] [$level] $msg_plain" >> "$log_file"

    case $level in
        INFO)  echo -e "${GRAY}[$timestamp_short]${NC} ${GREEN}[INFO]${NC}  $msg" ;;
        WARN)  echo -e "${GRAY}[$timestamp_short]${NC} ${YELLOW}[WARN]${NC}  $msg" ;;
        ERROR) echo -e "${GRAY}[$timestamp_short]${NC} ${RED}[ERROR]${NC} $msg" ;;
        DEBUG) echo -e "${GRAY}[$timestamp_short]${NC} ${CYAN}[DEBUG]${NC} $msg" ;;
        CMD)   echo -e "${GRAY}[$timestamp_short]${NC} ${GRAY}[CMD]${NC}   $msg" ;;
        *)     echo -e "${GRAY}[$timestamp_short]${NC} $msg" ;;
    esac
}

run_cmd() {
    local desc="$1"
    shift
    log CMD "$desc: $*"
    output=$("$@" 2>&1)
    local rc=$?
    if [ -n "$output" ]; then
        echo "$output" | while IFS= read -r line; do
            echo -e "           ${GRAY}│${NC} $line"
        done
    fi
    return $rc
}

show_help() {
    echo -e "${CYAN}Cistech VPN Connection Script${NC}"
    echo ""
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  -c, --connect     Connect to VPN and exit"
    echo "  -d, --disconnect  Disconnect VPN and exit"
    echo "  -m, --menu        Skip auto-connect, show menu directly"
    echo "  -r, --routes      Show routing table and exit"
    echo "  -s, --status      Show VPN status and exit"
    echo "  --help            Show this help message"
    echo ""
    echo "Menu Options:"
    echo "  1 - Connect VPN"
    echo "  2 - Disconnect VPN"
    echo "  3 - Show VPN status"
    echo "  4 - Setup IP forwarding"
    echo "  5 - Test connection"
    echo "  6 - Show network status"
    echo "  7 - Show routing table"
    echo "  8 - Show live TOTP"
    echo "  q - Quit"
}

# Fetch server certificate fingerprint
get_server_cert() {
    local host="$1"
    local server=$(echo "$host" | sed -E 's|^https?://||; s|/.*$||')
    [[ "$server" != *:* ]] && server="${server}:443"

    log DEBUG "Fetching server certificate from $server..."

    local cert_pin=$(echo | openssl s_client -connect "$server" 2>/dev/null \
        | openssl x509 -pubkey -noout 2>/dev/null \
        | openssl pkey -pubin -outform der 2>/dev/null \
        | openssl dgst -sha256 -binary \
        | openssl enc -base64)

    if [[ -n "$cert_pin" ]]; then
        echo "pin-sha256:${cert_pin}"
    else
        log WARN "Could not fetch server certificate"
        echo ""
    fi
}

# Setup keyring with credentials
setup_keyring() {
    log INFO "Setting up keyring credentials..."

    if [[ -z "$VPN_EMAIL" ]]; then
        log ERROR "VPN_EMAIL is not set"
        return 1
    fi

    python3 << PYTHON
import keyring
from keyrings.alt.file import PlaintextKeyring

keyring.set_keyring(PlaintextKeyring())

email = "$VPN_EMAIL"
password = "$VPN_PASSWORD"
totp_secret = "$VPN_TOTP_SECRET"

if password:
    keyring.set_password('openconnect-sso', email, password)
    print(f"Password stored in keyring for {email}")

if totp_secret:
    keyring.set_password('openconnect-sso', f'totp/{email}', totp_secret.upper())
    print(f"TOTP secret stored in keyring for {email}")

print("Keyring setup complete")
PYTHON

    if [ $? -eq 0 ]; then
        log INFO "Keyring credentials configured"
    else
        log ERROR "Failed to setup keyring"
        return 1
    fi
}

get_totp() {
    oathtool --totp -b "$VPN_TOTP_SECRET"
}

# Test connection to TARGET_IP
test_connection() {
    if [[ -z "$TARGET_IP" ]]; then
        log WARN "TARGET_IP not set"
        return 1
    fi

    log INFO "Testing connection to $TARGET_IP..."
    if ping -c 3 -W 3 "$TARGET_IP" &>/dev/null; then
        log INFO "Connection test: ${GREEN}SUCCESS${NC}"
        return 0
    else
        log WARN "Connection test: ${RED}FAILED${NC} (may need manual route on host)"
        return 1
    fi
}

show_totp() {
    log INFO "Starting live TOTP display (Ctrl+C to stop)"
    echo ""
    while true; do
        TOTP=$(get_totp)
        SECONDS_LEFT=$((30 - ($(date +%s) % 30)))
        echo -ne "\r           ${CYAN}Current TOTP:${NC} ${GREEN}$TOTP${NC} (expires in ${YELLOW}${SECONDS_LEFT}s${NC})   "
        sleep 1
    done
}

get_vpn_interface() {
    local iface=$(ip link show | grep -oP 'tun\d+(?=:.*UP)' | head -1)
    if [ -z "$iface" ]; then
        iface=$(ip link show | grep -oP 'tun\d+' | head -1)
    fi
    echo "$iface"
}

get_container_ip() {
    ip addr show eth0 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1
}

get_vpn_ip() {
    local iface=$(get_vpn_interface)
    if [ -n "$iface" ]; then
        ip addr show "$iface" 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1
    fi
}

check_vpn_status() {
    local vpn_iface=$(get_vpn_interface)
    if [ -n "$vpn_iface" ]; then
        local vpn_ip=$(get_vpn_ip)
        log INFO "VPN is ${GREEN}CONNECTED${NC}"
        log DEBUG "  Interface: $vpn_iface"
        log DEBUG "  VPN IP: $vpn_ip"
        return 0
    else
        log WARN "VPN is ${RED}NOT CONNECTED${NC}"
        return 1
    fi
}

show_routes() {
    echo -e "${CYAN}========================================${NC}"
    echo -e "${CYAN}         Current Routing Table          ${NC}"
    echo -e "${CYAN}========================================${NC}"
    echo ""
    echo -e "${GREEN}IPv4 Routes:${NC}"
    ip -4 route show | while IFS= read -r line; do
        if echo "$line" | grep -qE "tun[0-9]"; then
            echo -e "  ${YELLOW}$line${NC}"
        elif echo "$line" | grep -qE "10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\."; then
            echo -e "  ${CYAN}$line${NC}"
        else
            echo "  $line"
        fi
    done
    echo ""
    echo -e "${GREEN}VPN Interface:${NC}"
    local vpn_iface=$(get_vpn_interface)
    if [ -n "$vpn_iface" ]; then
        ip addr show "$vpn_iface" 2>/dev/null | grep -E "inet|link" | while IFS= read -r line; do
            echo "  $line"
        done
    else
        echo -e "  ${RED}No VPN interface found${NC}"
    fi
}

show_network_status() {
    log INFO "Current network status:"
    echo ""
    log DEBUG "Container Network Interfaces:"
    ip -4 addr show | grep -E "inet |^[0-9]+:" | while IFS= read -r line; do
        echo -e "           ${GRAY}│${NC} $line"
    done

    echo ""
    local vpn_iface=$(get_vpn_interface)
    if [ -n "$vpn_iface" ]; then
        local vpn_ip=$(get_vpn_ip)
        log INFO "VPN Status: ${GREEN}CONNECTED${NC}"
        log DEBUG "  Interface: $vpn_iface"
        log DEBUG "  VPN IP: $vpn_ip"
    else
        log WARN "VPN Status: ${RED}NOT CONNECTED${NC}"
    fi

    local container_ip=$(get_container_ip)
    if [ -n "$container_ip" ]; then
        log DEBUG "Container IP: $container_ip"
    fi

    echo ""
    log DEBUG "Default gateway:"
    ip route show default | while IFS= read -r line; do
        echo -e "           ${GRAY}│${NC} $line"
    done
    echo ""
}

kill_vpn_processes() {
    log INFO "Killing VPN processes..."
    local killed=0

    for pid in $(pgrep -x "openconnect" 2>/dev/null); do
        log DEBUG "Killing openconnect (PID $pid)"
        kill -9 "$pid" 2>/dev/null && ((killed++))
    done

    for pid in $(pgrep -f "openconnect-sso" 2>/dev/null); do
        log DEBUG "Killing openconnect-sso (PID $pid)"
        kill -9 "$pid" 2>/dev/null && ((killed++))
    done

    if [ $killed -eq 0 ]; then
        log INFO "No VPN processes were running"
    else
        log INFO "Killed $killed process(es)"
        sleep 2
    fi
}

disconnect_vpn() {
    log INFO "Disconnecting VPN..."
    kill_vpn_processes

    if check_vpn_status; then
        log WARN "VPN still appears connected"
        return 1
    fi

    log INFO "VPN disconnected"
    return 0
}

setup_forwarding() {
    log INFO "Setting up IP forwarding rules for $TARGET_SUBNET..."

    local vpn_iface=$(get_vpn_interface)
    if [ -z "$vpn_iface" ]; then
        log ERROR "No VPN interface found! Is VPN connected?"
        return 1
    fi

    local vpn_ip=$(get_vpn_ip)
    local container_ip=$(get_container_ip)

    log DEBUG "VPN interface: $vpn_iface"
    log DEBUG "VPN IP: $vpn_ip"
    log DEBUG "Container IP: $container_ip"

    # Enable IP forwarding
    run_cmd "Enabling IP forwarding" sysctl -w net.ipv4.ip_forward=1

    # NAT masquerade for container network going through VPN
    if ! iptables -t nat -C POSTROUTING -s "$CONTAINER_NETWORK" -o "$vpn_iface" -j MASQUERADE 2>/dev/null; then
        run_cmd "Adding NAT masquerade for container network -> VPN" iptables -t nat -A POSTROUTING -s "$CONTAINER_NETWORK" -o "$vpn_iface" -j MASQUERADE
    else
        log DEBUG "NAT masquerade for container network already exists"
    fi

    # Forward rules at position 1
    iptables -D FORWARD -d "$TARGET_SUBNET" -j ACCEPT 2>/dev/null || true
    iptables -D FORWARD -s "$TARGET_SUBNET" -j ACCEPT 2>/dev/null || true
    iptables -D FORWARD -s "$CONTAINER_NETWORK" -j ACCEPT 2>/dev/null || true
    iptables -D FORWARD -d "$CONTAINER_NETWORK" -j ACCEPT 2>/dev/null || true

    run_cmd "Inserting forward rule (to container network)" iptables -I FORWARD 1 -d "$CONTAINER_NETWORK" -j ACCEPT
    run_cmd "Inserting forward rule (from container network)" iptables -I FORWARD 1 -s "$CONTAINER_NETWORK" -j ACCEPT
    run_cmd "Inserting forward rule (from target)" iptables -I FORWARD 1 -s "$TARGET_SUBNET" -j ACCEPT
    run_cmd "Inserting forward rule (to target)" iptables -I FORWARD 1 -d "$TARGET_SUBNET" -j ACCEPT

    log INFO "Forwarding rules configured"
    echo ""

    # Trigger host routing service restart
    log INFO "Triggering host routing service restart..."
    touch /runtime/restart-routing 2>/dev/null || true
    sleep 2
    if [ ! -f /runtime/restart-routing ]; then
        log INFO "Host routing service restarted"
    else
        log WARN "Host watcher may not be running (trigger file still exists)"
    fi

    log INFO "Routing configured for $TARGET_SUBNET through VPN tunnel"
    echo ""
}

connect_vpn() {
    log INFO "=== Starting OpenConnect-SSO VPN ==="
    echo ""

    # Kill any existing VPN processes
    kill_vpn_processes

    # Reset DNS to public servers (VPN script may have overwritten it)
    log DEBUG "Resetting DNS to public servers..."
    echo "nameserver 8.8.8.8" > /etc/resolv.conf
    echo "nameserver 1.1.1.1" >> /etc/resolv.conf

    # Clean up stale tun interface
    ip link delete tun0 2>/dev/null || true

    # Validate required variables
    if [[ -z "$VPN_HOST" ]]; then
        log ERROR "VPN_HOST is not set"
        return 1
    fi

    # Setup keyring credentials
    setup_keyring

    # Fetch server certificate
    log INFO "Fetching server certificate..."
    local servercert=$(get_server_cert "$VPN_HOST")

    log INFO "Connecting to: $VPN_HOST"
    log DEBUG "Interface: $VPN_INTERFACE"

    # Build openconnect-sso command
    local sso_args=()
    sso_args+=("-s" "$VPN_HOST")

    if [[ -n "$VPN_EMAIL" ]]; then
        sso_args+=("-u" "$VPN_EMAIL")
    fi

    # Use hidden browser for automated login
    sso_args+=("--browser-display-mode" "hidden")

    # Build openconnect args
    local oc_args=()
    oc_args+=("--protocol=anyconnect")
    oc_args+=("--interface" "$VPN_INTERFACE")
    oc_args+=("--script" "/usr/share/vpnc-scripts/vpnc-script")

    if [[ -n "$servercert" ]]; then
        oc_args+=("--servercert" "$servercert")
        log DEBUG "Server cert: $servercert"
    fi

    # Launch openconnect-sso
    log INFO "Launching openconnect-sso..."
    openconnect-sso "${sso_args[@]}" -- /usr/sbin/openconnect "${oc_args[@]}" &
    OC_PID=$!
    disown $OC_PID
    log DEBUG "openconnect-sso started with PID $OC_PID"

    # Wait for VPN to connect
    log INFO "Waiting for VPN connection..."
    local wait_count=0
    local max_wait=300

    while [ -z "$(get_vpn_interface)" ]; do
        sleep 2
        ((wait_count+=2))
        if [ $((wait_count % 10)) -eq 0 ]; then
            log DEBUG "Still waiting for VPN... (${wait_count}s)"
        fi
        if [ $wait_count -ge $max_wait ]; then
            log ERROR "Timeout waiting for VPN connection after ${max_wait}s"
            return 1
        fi
    done

    log INFO "VPN connected!"
    local vpn_iface=$(get_vpn_interface)
    local vpn_ip=$(get_vpn_ip)
    log DEBUG "  Interface: $vpn_iface"
    log DEBUG "  VPN IP: $vpn_ip"

    # Wait for routes to stabilize
    log DEBUG "Waiting for routes to stabilize..."
    sleep 3

    # Setup forwarding
    setup_forwarding

    # Test connection to IBMI host
    test_connection

    # Disable screen blanking
    xset s off 2>/dev/null || true
    xset -dpms 2>/dev/null || true
    xset s noblank 2>/dev/null || true
    log DEBUG "Screen blanking disabled"

    log INFO "VPN setup complete"

    # Start watchdog in background
    start_watchdog &
    WATCHDOG_PID=$!
    log DEBUG "Watchdog started with PID $WATCHDOG_PID"

    return 0
}

# Watchdog - monitors VPN and reconnects if dropped
start_watchdog() {
    log INFO "Starting VPN watchdog (check every 60s, keepalive ping every 5min)..."

    local check_interval=60
    local keepalive_interval=300
    local last_keepalive=0
    local reconnect_attempts=0
    local max_reconnect_attempts=3

    while true; do
        sleep $check_interval

        local now=$(date +%s)
        local vpn_iface=$(get_vpn_interface)

        if [ -n "$vpn_iface" ]; then
            # VPN is up
            reconnect_attempts=0

            # Keepalive ping every 5 minutes
            if [ $((now - last_keepalive)) -ge $keepalive_interval ]; then
                test_connection || log WARN "VPN may be degraded"
                last_keepalive=$now
            fi
        else
            # VPN is down
            ((reconnect_attempts++))
            log WARN "VPN disconnected! Reconnect attempt $reconnect_attempts/$max_reconnect_attempts..."

            if [ $reconnect_attempts -le $max_reconnect_attempts ]; then
                connect_vpn
            else
                log ERROR "Max reconnect attempts reached. Manual intervention required."
                sleep 300
                reconnect_attempts=0
            fi
        fi
    done
}

# Main menu
main_menu() {
    echo -e "${GREEN}Options:${NC}"
    echo -e "  ${CYAN}1${NC} - Connect VPN"
    echo -e "  ${CYAN}2${NC} - Disconnect VPN"
    echo -e "  ${CYAN}3${NC} - Show VPN status"
    echo -e "  ${CYAN}4${NC} - Setup IP forwarding only"
    echo -e "  ${CYAN}5${NC} - Test connection to $TARGET_IP"
    echo -e "  ${CYAN}6${NC} - Show network status"
    echo -e "  ${CYAN}7${NC} - Show routing table"
    echo -e "  ${CYAN}8${NC} - Show live TOTP"
    echo -e "  ${CYAN}q${NC} - Quit"
    echo ""
}

parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            -c|--connect)
                DO_CONNECT=true
                shift
                ;;
            -d|--disconnect)
                DO_DISCONNECT=true
                shift
                ;;
            -m|--menu)
                SKIP_AUTO_CONNECT=true
                shift
                ;;
            -s|--status)
                print_banner
                check_vpn_status
                echo ""
                show_network_status
                exit 0
                ;;
            -r|--routes)
                show_routes
                exit 0
                ;;
            --help)
                show_help
                exit 0
                ;;
            *)
                echo "Unknown option: $1"
                echo "Use --help for usage information"
                exit 1
                ;;
        esac
    done

    if [ "$DO_CONNECT" = "true" ] && [ "$DO_DISCONNECT" = "true" ]; then
        echo "Error: --connect and --disconnect are mutually exclusive"
        exit 1
    fi
}

# Main
parse_args "$@"
cleanup_old_logs

echo "" >> "$(get_log_file)"
echo "========================================" >> "$(get_log_file)"
log INFO "=== Starting OpenConnect-SSO VPN ==="
echo ""
log DEBUG "VPN_EMAIL=$VPN_EMAIL"
log DEBUG "VPN_HOST=$VPN_HOST"
log DEBUG "TARGET_IP=$TARGET_IP"
log DEBUG "TARGET_SUBNET=$TARGET_SUBNET"
log DEBUG "VPN_TOTP_SECRET is $([ -n "$VPN_TOTP_SECRET" ] && echo 'set' || echo 'NOT SET')"

print_banner

if [ "$DO_DISCONNECT" = "true" ]; then
    disconnect_vpn
    exit $?
fi

if [ "$DO_CONNECT" = "true" ]; then
    connect_vpn
    exit $?
fi

# Auto-connect logic (unless -m flag)
if [ "$SKIP_AUTO_CONNECT" = "true" ]; then
    log INFO "Menu mode - skipping auto-connect"
elif check_vpn_status; then
    echo ""
    log INFO "VPN already connected. Setting up forwarding..."
    setup_forwarding
else
    echo ""
    log INFO "Auto-starting VPN connection..."
    echo ""
    connect_vpn
fi

# Interactive menu loop
while true; do
    echo ""
    main_menu
    echo -ne "${CYAN}Choice: ${NC}"
    read -r choice
    echo ""

    [[ -z "${choice// }" ]] && continue

    case $choice in
        1) connect_vpn ;;
        2) disconnect_vpn ;;
        3) check_vpn_status ;;
        4) setup_forwarding ;;
        5) test_connection ;;
        6) show_network_status ;;
        7) show_routes ;;
        8) show_totp ;;
        q|Q) log INFO "Goodbye!"; exit 0 ;;
        *) ;;
    esac
done
