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

WEBUI_PORT=${WEBUI_PORT:-8080}
export PATH="/usr/local/sbin:/usr/local/bin:${PATH}"

# --- Configuration & Pre-checks ---

if bashio::config.true 'silent'; then
    sed -i 's|/proc/1/fd/1 hassio;|off;|g' /etc/nginx/nginx.conf
fi

# --- Helper Functions ---

_fetch_public_ip() {
    local resp
    local url
    local urls=(
        "https://icanhazip.com"
        "https://ifconfig.me/ip"
        "https://api64.ipify.org"
        "https://checkip.amazonaws.com"
        "https://domains.google.com/checkip"
        "https://ipinfo.io/ip"
    )
    local shuffled_urls
    mapfile -t shuffled_urls < <(printf "%s\n" "${urls[@]}" | shuf)
    # Loop through the now-randomized list
    for url in "${shuffled_urls[@]}"; do
        resp=$(curl -fsS --max-time 5 "${url}" 2>/dev/null || true)
        resp="${resp//[[:space:]]/}"

        # Validation (IPv4 or IPv6 regex)
        if [[ "${resp}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || \
           [[ "${resp}" =~ ^[0-9a-fA-F:]+$ ]]; then
            printf '%s\n' "${resp}"
            return 0
        fi
    done
    return 1
}

_fetch_country_code() {
    local resp
    local url
    local urls=(
        "https://ipapi.co/country/"
        "http://ip-api.com/line/?fields=countryCode"
        "https://ifconfig.co/country-iso"
        "https://ipinfo.io/country"
        "https://www.icloud.com/geo/country_code/"
    )
    local shuffled_urls_output
    shuffled_urls_output=$(printf '%s\n' "${urls[@]}" | shuf)
    while IFS= read -r url; do
        # Skip empty lines if any
        [[ -z "${url}" ]] && continue
        # Fetch the response with a 5-second timeout
        resp=$(curl -fsS --max-time 5 "${url}" 2>/dev/null || true)
        # Clean whitespace/newlines
        resp="${resp//[[:space:]]/}"
        # Validation: Ensure the response is exactly 2 letters (ISO 3166-1 alpha-2)
        if [[ "${resp}" =~ ^[A-Za-z]{2}$ ]]; then
            # Convert to uppercase and print
            printf '%s\n' "${resp^^}"
            return 0
        fi
    done <<< "${shuffled_urls_output}" # Process the shuffled output
    return 1
}

_vpn_monitor_public_ip() {
    local vpn_label="${1:-VPN}"
    local current_ip_file="/currentip"
    local baseline_ip vpn_ip country
    local interval=${VPN_LEAK_CHECK_INTERVAL:-300}
    local initial_delay=${VPN_LEAK_INITIAL_DELAY:-60}

    # Pre-flight checks
    if ! command -v curl >/dev/null 2>&1; then
        bashio::log.warning "${vpn_label}: curl not found; VPN leak monitoring disabled."
        return 0
    fi

    if ! bashio::fs.file_exists "${current_ip_file}"; then
        bashio::log.warning "${vpn_label}: baseline IP file ${current_ip_file} not found; VPN leak monitoring disabled."
        return 0
    fi

    baseline_ip="$(tr -d '[:space:]' < "${current_ip_file}")"
    if [[ -z "${baseline_ip}" ]]; then
        bashio::log.warning "${vpn_label}: baseline IP in ${current_ip_file} is empty; VPN leak monitoring disabled."
        return 0
    fi

    bashio::log.info "${vpn_label}: Baseline (ISP) IP: ${baseline_ip}"
    bashio::log.debug "${vpn_label}: Waiting ${initial_delay}s before first leak check."
    sleep "${initial_delay}"

    while true; do
        vpn_ip="$(_fetch_public_ip || true)"
        
        if [[ -z "${vpn_ip}" ]]; then
            bashio::log.warning "${vpn_label}: Failed to fetch public IP (rate limited or connection down)."
        else
            if country="$(_fetch_country_code || true)"; then
                bashio::log.info "${vpn_label}: Current IP: ${vpn_ip} (${country})"
            else
                bashio::log.info "${vpn_label}: Current IP: ${vpn_ip} (Country Unknown)"
            fi

            # LEAK DETECTED
            if [[ "${vpn_ip}" == "${baseline_ip}" ]]; then
                bashio::log.fatal "${vpn_label}: VPN LEAK DETECTED! Current IP ${vpn_ip} matches baseline. Stopping container."
                s6-svscanctl -t /var/run/s6/services 2>/dev/null || true
                exit 1
            fi
        fi
        sleep "${interval}"
    done
}

# --- WireGuard Specific Logic ---

_setup_wireguard() {
    local WIREGUARD_STATE_DIR="/var/run/wireguard"
    local output=""
    local status=0

    if ! bashio::fs.file_exists "${WIREGUARD_STATE_DIR}/config"; then
        bashio::exit.nok 'WireGuard runtime configuration not prepared. Please restart the add-on.'
    fi

    local wireguard_config
    wireguard_config="$(cat "${WIREGUARD_STATE_DIR}/config")"
    local wireguard_interface
    wireguard_interface="$(cat "${WIREGUARD_STATE_DIR}/interface" 2>/dev/null || echo 'wg0')"

    if ip link show "${wireguard_interface}" &> /dev/null; then
        bashio::log.warning "WireGuard interface ${wireguard_interface} already exists. Resetting."
        wg-quick down "${wireguard_config}" >/dev/null 2>&1 || true
    fi

    bashio::log.info "Starting WireGuard interface ${wireguard_interface}..."

    # Internal helper: fallback for iptables-legacy
    _wg_prepare_legacy() {
        local legacy_bin_dir="${WIREGUARD_STATE_DIR}/iptables-legacy-bin"
        mkdir -p "${legacy_bin_dir}"
        local cmd
        for cmd in iptables iptables-save iptables-restore ip6tables ip6tables-save ip6tables-restore; do
            if command -v "${cmd}-legacy" >/dev/null 2>&1; then
                ln -sf "$(command -v "${cmd}-legacy")" "${legacy_bin_dir}/${cmd}"
            fi
        done
        chmod 700 "${legacy_bin_dir}" 2>/dev/null || true
        export PATH="${legacy_bin_dir}:${PATH}"
        bashio::log.warning 'Retrying WireGuard using iptables-legacy wrappers.'
    }

    # Internal helper: Attempt connection
    _wg_up_attempt() {
        local config_path="$1"
        output="$(wg-quick up "${config_path}" 2>&1)" || status=$?

        if [ "${status}" -eq 0 ]; then return 0; fi

        # Check for iptables errors and try legacy fallback
        if echo "${output}" | grep -qiE 'iptables-restore|ip6tables-restore|xtables'; then
            if command -v iptables-legacy >/dev/null 2>&1; then
                wg-quick down "${config_path}" >/dev/null 2>&1 || true
                _wg_prepare_legacy
                output="$(wg-quick up "${config_path}" 2>&1)" || status=$?
            else
                bashio::log.warning 'iptables errors detected but iptables-legacy missing.'
                status=1
            fi
        fi
        return "${status}"
    }

    # 1. First Attempt
    if ! _wg_up_attempt "${wireguard_config}"; then
        bashio::log.warning 'Initial WireGuard connection failed. Trying IPv4-only endpoints.'
        bashio::log.debug "Output: ${output}"

        # 2. IPv4 Fallback Preparation
        local ipv4_config="${WIREGUARD_STATE_DIR}/${wireguard_interface}-ipv4.conf"
        : > "${ipv4_config}"
        chmod 600 "${ipv4_config}" 2>/dev/null || true

        local line endpoint endpoint_host endpoint_port
        while IFS= read -r line || [ -n "$line" ]; do
            if [[ "${line}" =~ ^Endpoint ]]; then
                endpoint="${line#Endpoint = }"
                endpoint_host="${endpoint%:*}"
                endpoint_port="${endpoint##*:}"

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

                if [ ${#ipv4_candidates[@]} -gt 0 ]; then
                    bashio::log.debug "Resolved ${endpoint_host} to ${ipv4_candidates[0]}"
                    echo "Endpoint = ${ipv4_candidates[0]}:${endpoint_port}" >> "${ipv4_config}"
                else
                    echo "${line}" >> "${ipv4_config}"
                fi
            else
                echo "${line}" >> "${ipv4_config}"
            fi
        done < "${wireguard_config}"

        wg-quick down "${wireguard_config}" >/dev/null 2>&1 || true

        # 3. Second Attempt (IPv4 only)
        if ! _wg_up_attempt "${ipv4_config}"; then
            bashio::log.error 'WireGuard failed to establish connection.'
            bashio::log.error "${output}"
            bashio::exit.nok 'WireGuard start failed.'
        fi
    fi

    bashio::log.info "WireGuard interface ${wireguard_interface} is up."
    
    # DNS Refresh
    if command -v resolvconf >/dev/null 2>&1; then
        resolvconf -u >/dev/null 2>&1 || bashio::log.warning 'resolvconf -u failed.'
    fi
}

# --- Main Execution ---

echo "$(_fetch_public_ip || true)" > /currentip

if bashio::config.true 'openvpn_enabled'; then
    # Start Leak Monitor
    if [[ -s /currentip ]]; then
        bashio::log.warning "Could not fetch public ip, the vpn leakage service will not run"
    else
        _vpn_monitor_public_ip "OpenVPN" &
    fi

    exec /usr/sbin/openvpn \
        --config /config/openvpn/config.ovpn \
        --script-security 2 \
        --up /etc/openvpn/up.sh \
        --down /etc/openvpn/down.sh \
        --pull-filter ignore "route-ipv6" \
        --pull-filter ignore "ifconfig-ipv6" \
        --pull-filter ignore "tun-ipv6" \
        --pull-filter ignore "redirect-gateway ipv6" \
        --pull-filter ignore "dhcp-option DNS6"

elif bashio::config.true 'wireguard_enabled'; then
    
    # Run modularized WireGuard setup
    _setup_wireguard

    # Start Leak Monitor
    if [[ -s /currentip ]]; then
        bashio::log.warning "Could not fetch public ip, the vpn leakage service will not run"
    else
        _vpn_monitor_public_ip "WireGuard" &
    fi

fi

# --- Launch qBittorrent ---

# Determine log output based on silent mode
QB_OUTPUT="/dev/stdout"
if bashio::config.true 'silent'; then
    QB_OUTPUT="/dev/null"
fi

bashio::log.info "Starting qBittorrent..."

exec \
    s6-notifyoncheck -d -n 300 -w 1000 -c "nc -z localhost ${WEBUI_PORT}" \
    s6-setuidgid abc /usr/bin/qbittorrent-nox --webui-port="${WEBUI_PORT}" > "${QB_OUTPUT}"
