#!/usr/bin/with-contenv bashio
# shellcheck shell=bash

# --- Common Functions ---

declare -A config
config["MySelf"]="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"

declare -a dns_servers_ipv4=()
declare -a dns_servers_ipv6=()

log_level="$(bashio::config "log_level")"
[[ "$log_level" == "debug" ]] && bashio::log.warning "--- Debug mode is active ---"
[[ "$log_level" == "debug" ]] && set -x

_parse_config() {
    local -n config_ref="$1"
    local config_file="$2"
    local line

    while IFS= read -r line || [[ -n "$line" ]]; do
        # Skip comments and empty lines
        [[ "$line" =~ ^[#!] ]] && continue
        # Extract key and value using regex (trim spaces)
        #if [[ "$line" =~ ^[[:space:]]*([^ =]+)[[:space:]]*=[[:space:]]*(.*)[[:space:]]* ]]; then
        if [[ "$line" =~ ^[[:space:]]*([^=[:space:]]+)[=[:space:]]+(.*)[[:space:]]* ]]; then
            local key="${BASH_REMATCH[1]}"
            local value="${BASH_REMATCH[2]}"
            if [[ "$key" == "Address" ]]; then
                if [[ -n "${config_ref["Address"]:-}" ]]; then
                    config_ref["Address"]+=",${value}"
                else
                    config_ref["Address"]="${value}"
                fi
            else
                config_ref["$key"]="$value"
            fi
        fi
    done < "$config_file"
}

_parse_dns() {
    local dns_ip
    local -a dns_conf=()
    local -a dns_backup_ipv4=("8.8.8.8" "1.1.1.1")
    local -a dns_backup_ipv6=("2001:4860:4860::8888" "2606:4700:4700::1111")
    local dns_servers=$(bashio::config 'DNS_server')

    mapfile -d ',' -t dns_conf < <(echo "${dns_servers}" | tr -d ' ' | tr -d '\n')
    if [ ${config["IPv4Enabled"]} = "true" ]; then
        for dns_ip in "${dns_conf[@]}"; do
            local result=0
            _check_host "${dns_ip}" || result=$?
            if [ "${result}" -eq 1 ]; then
                dns_servers_ipv4+=("${dns_ip}")
            fi
        done
        if [ ${#dns_servers_ipv4[@]} -eq 0 ]; then
            bashio::log.warning "No valid IPv4 DNS servers configured. Using addon defaults ${dns_backup_ipv4[@]}"
            dns_servers_ipv4=("${dns_backup_ipv4[@]}")
        fi
    fi
    if [ ${config["IPv6Enabled"]} = "true" ]; then
        for dns_ip in "${dns_conf[@]}"; do
            local result=0
            _check_host "${dns_ip}" || result=$?
            if [ "${result}" -eq 2 ]; then
                dns_servers_ipv6+=("${dns_ip}")
            fi
        done
        if [ ${#dns_servers_ipv6[@]} -eq 0 ]; then
            bashio::log.warning "No valid IPv6 DNS servers configured. Using addon defaults ${dns_backup_ipv6[@]}"
            dns_servers_ipv6=("${dns_backup_ipv6[@]}")
        fi
    fi
}

_cmd() {
    cmd="$1"
    bashio::log.debug "Executing command: ${cmd}"
    eval "${cmd}"
}

_check_host() {
    if ipcalc -c -4 "$1" >/dev/null 2>&1; then
        return 1  # IPv4
    elif ipcalc -c -6 "$1" >/dev/null 2>&1; then
        return 2  # IPv6
    elif getent ahosts "$1" >/dev/null 2>&1; then
        return 3  # resolvable hostnamee
    else
        return 0  # neither IP nor resolvable hostname
    fi
}

_resolvconf() {
    local mode=$1
    local resolv_conf="/etc/resolv.conf"
    local resolv_backup="/etc/resolv.conf.bak"

    if [ "${mode}" = "reset" ]; then
        bashio::log.info "Resetting ${resolv_conf} to default DNS servers."
        if bashio::fs.file_exists "${resolv_backup}"; then
            cp "${resolv_backup}" "${resolv_conf}"
        else
            bashio::log.warning "No original resolv.conf backup found. Leaving as is."
        fi
    elif [ "${mode}" = "update" ]; then
        bashio::log.info "Updating ${resolv_conf} with VPN DNS servers."
        if ! bashio::fs.file_exists "${resolv_backup}"; then
            bashio::log.debug "Creating backup of original resolv.conf at ${resolv_backup}"
            cp "${resolv_conf}" "${resolv_backup}" 2>/dev/null || true
        fi
        bashio::log.debug "Updating ${resolv_conf} with DNS servers: ${dns_servers_ipv4[*]} ${dns_servers_ipv6[*]}"
        {
            echo "# Generated by vpn script"
            local dns_ip
            for dns_ip in ${dns_servers_ipv4[@]} ${dns_servers_ipv6[@]}; do
                echo "nameserver ${dns_ip}"
            done
        } > "${resolv_conf}"
    else
        bashio::exit.nok "Invalid resolvconf mode specified. Use 'update' or 'reset'."
    fi
}

_resolve_hostname() {
    local hostname=$1
    local -a ips=()
    local -a ipv4_candidates=()
    local -a ipv6_candidates=()

    # Resolve hostname to IPv4
    mapfile -t ipv4_candidates < <(getent ahostsv4 "${hostname}" | awk '{print $1}' | uniq)

    # Resolve hostname to IPv6
    mapfile -t ipv6_candidates < <(getent ahostsv6 "${hostname}" | awk '{print $1}' | uniq)

    if [ ${#ipv4_candidates[@]} -gt 0 ]; then
        bashio::log.debug "Resolved ${hostname} to ${ipv4_candidates[@]}"
        ips+=("${ipv4_candidates[@]}")
    fi

    if [ ${#ipv6_candidates[@]} -gt 0 ]; then
        bashio::log.debug "Resolved ${hostname} to ${ipv6_candidates[@]}"
        ips+=("${ipv6_candidates[@]}")
    fi

    echo "${ips[@]}"
}

_routing_add() {
    bashio::log.info "Adding routing rules for VPN interface ${config["Interface"]}..."

    local local_ipv4=$(ip addr show ${config["Interface"]} | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)
    local local_ipv6=$(ip addr show ${config["Interface"]} | grep 'inet6 ' | awk '{print $2}' | cut -d'/' -f1)
    local ipv4
    local ipv6
    local dns_ip

    # add routing rules for local IPs
    for ipv4 in ${local_ipv4}; do
        config["IPv4Enabled"]="true"
        _cmd "ip -4 rule add priority 1 from ${ipv4} table ${config["Table"]}" || return 1
        _cmd "ip -4 rule add priority 1 to ${ipv4}/24 table ${config["Table"]}" || return 1
    done
    if [ "${config["IPv4Enabled"]}" = "true" ]; then
        _cmd "ip -4 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1
    fi
    for ipv6 in ${local_ipv6}; do
        config["IPv6Enabled"]="true"
        _cmd "ip -6 rule add priority 1 from ${ipv6} table ${config["Table"]}" || return 1
        _cmd "ip -6 rule add priority 1 to ${ipv6}/64 table ${config["Table"]}" || return 1
    done
    if [ "${config["IPv6Enabled"]}" = "true" ]; then
        _cmd "ip -6 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1
    fi

    # get valid DNS servers
    _parse_dns

    # add routing rules for DNS servers
    for dns_ip in "${dns_servers_ipv4[@]}"; do
        #_cmd "ip -4 route add ${dns_ip} dev ${config["Interface"]}" || return 1
        _cmd "ip -4 rule add priority 1 to ${dns_ip} table ${config["Table"]}" || return 1
    done
    for dns_ip in "${dns_servers_ipv6[@]}"; do
        #_cmd "ip -6 route add ${dns_ip} dev ${config["Interface"]}" || return 1
        _cmd "ip -6 rule add priority 1 to ${dns_ip} table ${config["Table"]}" || return 1
    done
}

_routing_del() {
    bashio::log.info "Removing routing rules for VPN interface ${config["Interface"]}..."

    while _cmd "ip -4 rule del priority 1 from all table ${config["Table"]} 2>/dev/null"; do :; done
    while _cmd "ip -4 rule del priority 1 to all table ${config["Table"]} 2>/dev/null"; do :; done
    while _cmd "ip -4 route del default dev ${config["Interface"]} table ${config["Table"]} 2>/dev/null"; do :; done
    while _cmd "ip -6 rule del priority 1 from all table ${config["Table"]} 2>/dev/null"; do :; done
    while _cmd "ip -6 rule del priority 1 to all table ${config["Table"]} 2>/dev/null"; do :; done
    while _cmd "ip -6 route del default dev ${config["Interface"]} table ${config["Table"]} 2>/dev/null"; do :; done
}

# --- Firewall Specific Functions ---

_firewall_add() {
    if [ "${config["IPv4Enabled"]}" = "true" ]; then
        _cmd "iptables -N pnat" || return 1
        _cmd "iptables -A INPUT -i ${config["Interface"]} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT" || return 1
        _cmd "iptables -A INPUT -i ${config["Interface"]} -p icmp -j ACCEPT" || return 1
        _cmd "iptables -A INPUT -i ${config["Interface"]} -j pnat" || return 1
        _cmd "iptables -A INPUT -i ${config["Interface"]} -j DROP" || return 1
    fi

    if [ "${config["IPv6Enabled"]}" = "true" ]; then
        _cmd "ip6tables -N pnat" || return 1
        _cmd "ip6tables -A INPUT -i ${config["Interface"]} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT" || return 1
        _cmd "ip6tables -A INPUT -i ${config["Interface"]} -p icmpv6 -j ACCEPT" || return 1
        _cmd "ip6tables -A INPUT -i ${config["Interface"]} -j pnat" || return 1
        _cmd "ip6tables -A INPUT -i ${config["Interface"]} -j DROP" || return 1
    fi
}

_firewall_del() {
    if [ "${config["IPv4Enabled"]}" = "true" ]; then
        _cmd "iptables -F INPUT" || true
        _cmd "iptables -F pnat" || true
        _cmd "iptables -X pnat" || true
    fi

    if [ "${config["IPv6Enabled"]}" = "true" ]; then
        _cmd "ip6tables -F INPUT" || true
        _cmd "ip6tables -F pnat" || true
        _cmd "ip6tables -X pnat" || true
    fi
}

# --- WireGuard Specific Logic ---

_wireguard_check() {
    local timeout="${1:-20}"
    local deadline ts

    deadline=$(( $(date +%s) + timeout ))

    while [ "$(date +%s)" -lt "${deadline}" ]; do
        ping -I "${config["Interface"]}" -c1 -W1 1.1.1.1 >/dev/null 2>&1 || true

        ts="$(wg show "${config["Interface"]}" latest-handshakes 2>/dev/null | awk -v pk="${config["PublicKey"]}" '$1==pk{print $2; exit}')"
        if [ -n "${ts}" ] && [ "${ts}" -gt 0 ] 2>/dev/null; then
            return 0
        fi
        sleep 1
    done

    bashio::log.error "WireGuard handshake not established after ${timeout}s (latest-handshake=${ts:-0})."
    wg show "${config["Interface"]}" 2>&1 | while IFS= read -r l; do bashio::log.error "${l}"; done
    return 1
}

_wireguard_up() {
    local local_ip
    local -a local_ips=()
    local -A local_ip_types=()
    local allowed_ip
    local -a allowed_ips=()
    local -A allowed_ip_types=()
    local key

    bashio::log.warning "This script force Wireguard to ignore any routes and DNS settings."
    bashio::log.warning "Default route will be inserted into custom routing table: ${config["Table"]}"
    bashio::log.warning "This routing table will be used for traffic from the VPN interface and to the configured DNS servers."
    bashio::log.warning "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only."

    for key in "Interface" "ListenPort" "PrivateKey" "PublicKey" "EndpointIP" "EndpointPort" "Address"; do
        if [ ! -v config[$key] ] || [ -z "${config[$key]}" ]; then
            bashio::log.error "Missing required WireGuard configuration parameter: ${key}"
            return 1
        fi
    done

    _cmd "ip link add ${config["Interface"]} type wireguard" || return 1

    mapfile -d ',' -t local_ips < <(echo "${config["Address"]}" | tr -d ' ')
    for local_ip in ${local_ips[@]}; do
        local result=0
        _check_host "${local_ip}" || result=$?
        if [ "${result}" -eq 1 ]; then
            config["IPv4Enabled"]="true"
            local_ip_types["${local_ip}"]="ipv4"
            allowed_ip_types["0.0.0.0/0"]="ipv4"
            _cmd "ip addr add ${local_ip} dev ${config["Interface"]}" || return 1
        elif [ "${result}" -eq 2 ]; then
            config["IPv6Enabled"]="true"
            local_ip_types["${local_ip}"]="ipv6"
            _cmd "ip addr add ${local_ip} dev ${config["Interface"]}" || return 1
        else
            bashio::log.warning "Ignoring invalid local IP address: ${local_ip}"
        fi
    done
    if [ ${#local_ip_types[@]} -eq 0 ]; then
        bashio::log.error "No valid local IP addresses configured for WireGuard interface."
        return 1
    fi

    mapfile -d ',' -t allowed_ips < <(echo "${config["Address"]}" | tr -d ' ')
    for allowed_ip in ${allowed_ips[@]}; do
        local result=0
         _check_host "${allowed_ip}" || result=$?
        if [ "${result}" -eq 1 ] && [ "${config["IPv4Enabled"]}" == "true" ]; then
            allowed_ip_types["${allowed_ip}"]="ipv4"
            #allowed_ip_types["0.0.0.0/0"]="ipv4"
        elif [ "${result}" -eq 2 ] && [ "${config["IPv6Enabled"]}" == "true" ]; then
            allowed_ip_types["${allowed_ip}"]="ipv6"
            #allowed_ip_types["::/0"]="ipv6"
        else
            bashio::log.error "Invalid allowed IP address: ${allowed_ip}"
            return 1
        fi
    done
    if [ ${#allowed_ip_types[@]} -eq 0 ]; then
        bashio::log.error "No valid allowed IP addresses configured for WireGuard peer."
        return 1
    fi
    printf -v allowed_ips '%s,' "${!allowed_ip_types[@]}"
    allowed_ips="${allowed_ips%,}"

    _cmd "wg set ${config["Interface"]} listen-port ${config["ListenPort"]} private-key ${config["PrivateKey"]}" || return 1
    local endpoint="${config["EndpointIP"]}:${config["EndpointPort"]}"
    if [[ "${config["EndpointIP"]}" == *:* ]]; then
      endpoint="[${config["EndpointIP"]}]:${config["EndpointPort"]}"
    fi
    local peer_cmd="wg set ${config["Interface"]} peer ${config["PublicKey"]} endpoint ${endpoint} allowed-ips ${allowed_ips}"
    if [ -n "${config["PresharedKey"]:-}" ]; then
      peer_cmd="${peer_cmd} preshared-key ${config["PresharedKey"]}"
    fi
    if [ -n "${config["PersistentKeepalive"]:-}" ]; then
      peer_cmd="${peer_cmd} persistent-keepalive ${config["PersistentKeepalive"]}"
    fi
    _cmd "${peer_cmd}" || return 1

    if [ -v config["MTU"] ] && [ -n "${config["MTU"]}" ]; then
        _cmd "ip link set ${config["Interface"]} mtu ${config["MTU"]}" || return 1
    fi

    _cmd "ip link set ${config["Interface"]} up" || return 1

    # Add routing rules for VPN interface and DNS servers
    _routing_add || return 1
    # Add firewall rules for VPN interface (only when UPnP port mapping is enabled)
    if bashio::config.true 'vpn_upnp_enabled'; then
        _firewall_add || bashio::log.warning "Firewall rules could not be applied (non-fatal)."
    fi
    # Update resolv.conf with VPN DNS servers
    _resolvconf "update" || return 1
    # Wait for handshake to be established before returning success
    _wireguard_check 10 || return 1
}

_wireguard_down() {
    # Update resolv.conf to remove VPN DNS servers
    _resolvconf "reset" || true
    # Remove routing rules for VPN interface and DNS servers
    _routing_del || true
    # Remove firewall rules for VPN interface (only when UPnP port mapping is enabled)
    if bashio::config.true 'vpn_upnp_enabled'; then
        _firewall_del || true
    fi

    _cmd "ip link set ${config["Interface"]} down" 2>/dev/null || true
    _cmd "ip link del ${config["Interface"]}" 2>/dev/null || true
}

wireguard() {
    local mode=$1
    local key
    local interface
    local config_file
    local WIREGUARD_STATE_DIR="/var/run/wireguard"

    if ! bashio::fs.file_exists "${WIREGUARD_STATE_DIR}/interface"; then
        bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.'
    fi
    interface=$(cat "${WIREGUARD_STATE_DIR}/interface")
    if [ -z "${interface}" ]; then
        bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.'
    fi
    if ! bashio::fs.file_exists "${WIREGUARD_STATE_DIR}/config"; then
        bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.'
    fi
    config_file=$(cat "${WIREGUARD_STATE_DIR}/config")
    if [ -z "${config_file}" ]; then
        bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.'
    fi

    bashio::log.info "Using Wireguard configuration file: ${config_file}"

    _parse_config config "${config_file}"
    config["Interface"]="${interface}"
    config["ConfigFile"]="${config_file}"
    config["Table"]="${config["Table"]:-1000}"
    config["ListenPort"]="${config["ListenPort"]:-51820}"
    config["EndpointHost"]="${config["Endpoint"]%:*}"
    config["EndpointPort"]="${config["Endpoint"]##*:}"
    config["IPv4Enabled"]="false"
    config["IPv6Enabled"]="false"
    for key in "${!config[@]}"; do
        bashio::log.debug "${key}: ${config[$key]}"
    done

    printf '%s\n' "${config["PrivateKey"]}" > "${WIREGUARD_STATE_DIR}/privatekey"
    chmod 600 "${WIREGUARD_STATE_DIR}/privatekey" || true
    config["PrivateKey"]="${WIREGUARD_STATE_DIR}/privatekey"

    if [ -n "${config["PresharedKey"]:-}" ]; then
      printf '%s\n' "${config["PresharedKey"]}" > "${WIREGUARD_STATE_DIR}/presharedkey"
      chmod 600 "${WIREGUARD_STATE_DIR}/presharedkey" || true
      config["PresharedKey"]="${WIREGUARD_STATE_DIR}/presharedkey"
    fi

    if [ "${mode}" = "up" ]; then
        bashio::log.info "Starting WireGuard on interface ${config["Interface"]}..."
        local result=0
        _check_host "${config["EndpointHost"]}" || result=$?
        if [ "${result}" -eq 0 ]; then
            bashio::log.error "WireGuard endpoint ${config["EndpointHost"]} is neither a valid IP address nor a resolvable hostname."
            bashio::exit.nok 'WireGuard start failed.'
        elif [ "${result}" -eq 3 ]; then
            local -a endpoint_ips=()
            mapfile -d ' ' -t endpoint_ips < <(_resolve_hostname ${config["EndpointHost"]})
            if [ ${#endpoint_ips[@]} -eq 0 ]; then
                bashio::log.error "Failed to resolve WireGuard endpoint hostname: ${config["EndpointHost"]}"
                bashio::exit.nok 'WireGuard start failed.'
            fi
            for endpoint_ip in "${endpoint_ips[@]}"; do
                bashio::log.info "Resolved WireGuard endpoint hostname ${config["EndpointHost"]} to IP: ${endpoint_ip}"
                config["EndpointIP"]="${endpoint_ip}"
                if _wireguard_up; then
                    bashio::log.info "WireGuard interface ${config["Interface"]} is up."
                    bashio::exit.ok 'WireGuard started.'
                fi
                bashio::log.error 'WireGuard failed to establish connection.'
                _wireguard_down
            done
        else
            bashio::log.debug "WireGuard endpoint ${config["EndpointHost"]} is a valid IP address. Using as is."
            config["EndpointIP"]="${config["EndpointHost"]}"
            if _wireguard_up; then
                bashio::log.info "WireGuard interface ${config["Interface"]} is up."
                bashio::exit.ok 'WireGuard started.'
            fi
            bashio::log.error 'WireGuard failed to establish connection.'
            _wireguard_down
        fi
    elif [ "${mode}" = "down" ]; then
        bashio::log.info "Stopping WireGuard on interface ${config["Interface"]}..."
        _wireguard_down
        bashio::log.info "WireGuard on interface ${config["Interface"]} is down."
        bashio::exit.ok 'WireGuard stopped.'
    else
        bashio::log.error "Invalid WireGuard mode specified. Use 'up' or 'down'."
        bashio::exit.nok 'WireGuard start failed.'
    fi

    bashio::exit.nok 'WireGuard start failed.'
}

# --- OpenVPN Specific Logic ---

_openvpn_check() {
    local timeout="${1:-20}"
    local deadline ts

    deadline=$(( $(date +%s) + timeout ))

    while [ "$(date +%s)" -lt "${deadline}" ]; do
        if ip link show "${config["Interface"]}" > /dev/null 2>&1 ; then
            return 0
        fi
        sleep 2
    done

    bashio::log.error "OpenVPN interface ${config["Interface"]} failed to come up after ${timeout}s."
    return 1
}

_openvpn_up() {
    bashio::log.warning "This script force OpenvPN to ignore any routes and DNS settings pushed by the server."
    bashio::log.warning "Default route will be inserted into custom routing table: ${config["Table"]}"
    bashio::log.warning "This routing table will be used for traffic from the VPN interface and to the configured DNS servers."
    bashio::log.warning "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only."

    # Register this script as OpenVPN up/down handlers to manage routing
    echo '#!/bin/bash' > ${config["PostUpScript"]}
    echo "${config["MySelf"]} openvpn postup" >> ${config["PostUpScript"]}
    chmod 755 ${config["PostUpScript"]}
    echo '#!/bin/bash' > ${config["PostDownScript"]}
    echo "${config["MySelf"]} openvpn postdown" >> ${config["PostDownScript"]}
    chmod 755 ${config["PostDownScript"]}

    # Define logging
    declare -A verbosity=(
        ["fatal"]=1
        ["error"]=1
        ["warning"]=2
        ["notice"]=3
        ["info"]=4
        ["debug"]=5
        ["trace"]=6
    )

    # Start OpenVPN in the background
    _cmd "/usr/sbin/openvpn \
        --config "${config["ConfigFile"]}" \
        --client \
        --daemon \
        --verb ${verbosity[${log_level}]} \
        --log "/proc/1/fd/1" \
        --script-security 2 \
        --auth-user-pass "${OPENVPN_STATE_DIR}/credentials.conf" \
        --auth-retry none \
        --up ${config["PostUpScript"]} \
        --down ${config["PostDownScript"]} \
        --up-delay  \
        --up-restart \
        --route-nopull \
        --route-noexec" || return 1

    # Wait for the VPN interface to come up
    _openvpn_check 30 || return 1
}

_openvpn_down() {
    # Terminate OpenVPN process
    pkill -f "openvpn --config ${config["ConfigFile"]}" || true
    # Safety-net cleanup in case the --down callback was never invoked
    _routing_del || true
}

_openpvn_postup() {
    # Add routing rules for VPN interface and DNS servers
    _routing_add || return 1
    # Add firewall rules for VPN interface (only when UPnP port mapping is enabled)
    if bashio::config.true 'vpn_upnp_enabled'; then
        _firewall_add || bashio::log.warning "Firewall rules could not be applied (non-fatal)."
    fi
    # Update resolv.conf with VPN DNS servers
    _resolvconf "update" || return 1
}

_openpvn_postdown() {
    # Update resolv.conf to remove VPN DNS servers
    _resolvconf "reset" || true
    # Remove routing rules for VPN interface and DNS servers
    _routing_del || true
    # Remove firewall rules for VPN interface (only when UPnP port mapping is enabled)
    if bashio::config.true 'vpn_upnp_enabled'; then
        _firewall_del || true
    fi
}

openvpn() {
    local mode=$1
    local interface
    local config_file
    local OPENVPN_STATE_DIR="/var/run/openvpn"

    if ! bashio::fs.file_exists "${OPENVPN_STATE_DIR}/interface"; then
        bashio::exit.nok 'OpenVPN runtime configuration not prepared. Please restart the add-on.'
    fi
    interface=$(cat "${OPENVPN_STATE_DIR}/interface")
    if [ -z "${interface}" ]; then
        bashio::exit.nok 'OpenVPN runtime configuration not prepared. Please restart the add-on.'
    fi
    if ! bashio::fs.file_exists "${OPENVPN_STATE_DIR}/config"; then
        bashio::exit.nok 'OpenVPN runtime configuration not prepared. Please restart the add-on.'
    fi
    config_file=$(cat "${OPENVPN_STATE_DIR}/config")
    if [ -z "${config_file}" ]; then
        bashio::exit.nok 'OpenVPN runtime configuration not prepared. Please restart the add-on.'
    fi

    bashio::log.warning "Using OpenVPN configuration file: ${config_file}"

    _parse_config config "${config_file}"
    config["Interface"]="${interface}"
    config["ConfigFile"]="${config_file}"
    config["Table"]="${config["Table"]:-1000}"
    config["PostUpScript"]="${OPENVPN_STATE_DIR}/up.sh"
    config["PostDownScript"]="${OPENVPN_STATE_DIR}/down.sh"

    if [ "${mode}" = "up" ]; then
        # register up and down scripts
        bashio::log.info "Starting OpenVPN on interface ${config["Interface"]}..."
        if _openvpn_up; then
            bashio::log.info "OpenVPN interface ${config["Interface"]} is up."
            bashio::exit.ok 'OpenVPN started.'
        fi
        bashio::log.error 'OpenVPN failed to establish connection.'
        _openvpn_down
    elif [ "${mode}" = "down" ]; then
        bashio::log.info "Stopping OpenVPN on interface ${config["Interface"]}..."
        _openvpn_down
        bashio::log.info "OpenVPN on interface ${config["Interface"]} is down."
        bashio::exit.ok 'OpenVPN stopped.'
    elif [ "${mode}" = "postup" ]; then
        _openpvn_postup
        bashio::exit.ok 'OpenVPN routes added.'
    elif [ "${mode}" = "postdown" ]; then
        _openpvn_postdown
        bashio::exit.ok 'OpenVPN routes deleted.'
    else
        bashio::log.error "Invalid OpenVPN mode specified. Use 'up', 'down', 'postup', or 'postdown'."
        bashio::exit.nok 'OpenVPN start failed.'
    fi

    bashio::exit.nok 'OpenVPN start failed.'
}

# --- Entry Point ---

if [ $# -ne 2 ]; then
    bashio::log.error "Invalid number of arguments. Usage: vpn.sh <wireguard|openvpn> <up|down>"
    bashio::exit.nok 'VPN start failed.'
fi
if [[ "$1" == "wireguard" ]]; then
    wireguard "$2"
elif [[ "$1" == "openvpn" ]]; then
    openvpn "$2"
else
    bashio::log.error "Invalid VPN type specified. Use 'wireguard' or 'openvpn'."
    bashio::exit.nok 'VPN start failed.'
fi
