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

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

REAL_IP_FILE="/currentip"
VPN_CHECK_INTERVAL="${VPN_CHECK_INTERVAL:-300}"
VPN_INFO_URL="${VPN_INFO_URL:-https://ipinfo.io}"

# -------------------------------
# Helpers
# -------------------------------

_fetch_public_ip() {
    local resp
    local url
    local curl_iface_opts=()
    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

    if [[ -n "${VPN_INTERFACE:-}" ]]; then
        curl_iface_opts+=(--interface "${VPN_INTERFACE}")
    fi

    mapfile -t shuffled_urls < <(printf "%s\n" "${urls[@]}" | shuf)

    for url in "${shuffled_urls[@]}"; do
        resp=$(curl -fsS --max-time 5 "${curl_iface_opts[@]}" "${url}" 2>/dev/null || true)
        resp="${resp//[[:space:]]/}"

        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
}

read_real_ip() {
    local attempt ip

    for attempt in {1..6}; do
        if [[ -f "${REAL_IP_FILE}" ]]; then
            ip="$(tr -d '[:space:]' < "${REAL_IP_FILE}")"
            if [[ -n "${ip}" ]]; then
                echo "${ip}"
                return 0
            fi
        fi
        sleep 5
    done

    echo ""
    return 0
}

get_ip_info() {
    local ip country json info_url url

    if ! ip="$(_fetch_public_ip)"; then
        bashio::log.warning "Unable to determine external IP from fallback IP services."
        return 1
    fi

    country="Unknown"
    info_url="${VPN_INFO_URL:-}"

    if [[ -n "${info_url}" ]]; then
        if [[ "${info_url}" == *"%s"* ]]; then
            printf -v url "${info_url}" "${ip}"
        else
            url="${info_url%/}/${ip}/json"
        fi

        if json="$(curl -fsS --max-time 10 "${url}" 2>/dev/null || true)" && [[ -n "${json}" ]]; then
            country="$(
                printf '%s\n' "${json}" \
                    | sed -n 's/.*"country"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
                    | head -n1
            )"
            [[ -z "${country}" ]] && country="Unknown"
        fi
    fi

    printf '%s %s\n' "${ip}" "${country}"
}

wait_for_vpn_ip() {
    local attempt out ip country

    for attempt in {1..5}; do
        if out="$(get_ip_info)"; then
            read -r ip country <<< "${out}"

            if [[ -n "${REAL_IP:-}" ]] && [[ "${ip}" == "${REAL_IP}" ]]; then
                bashio::log.warning \
                    "External IP still equals real IP (${ip}); VPN not ready yet (attempt ${attempt}/5)."
            else
                printf '%s %s\n' "${ip}" "${country}"
                return 0
            fi
        else
            bashio::log.warning "Unable to query external IP (attempt ${attempt}/5)."
        fi

        sleep 5
    done

    return 1
}

# -------------------------------
# Main logic
# -------------------------------

vpn_openvpn=false
vpn_wireguard=false

if bashio::config.true 'openvpn_enabled'; then
    vpn_openvpn=true
fi

if bashio::config.true 'wireguard_enabled'; then
    vpn_wireguard=true
fi

if [[ "${vpn_openvpn}" != true && "${vpn_wireguard}" != true ]]; then
    bashio::log.info "VPN leak monitor not started because no VPN is enabled."
    exit 0
fi

if [[ "${vpn_openvpn}" == true && "${vpn_wireguard}" == true ]]; then
    bashio::log.error "Both OpenVPN and WireGuard are enabled. Only one VPN mode is supported."
    bashio::addon.stop
    exit 1
fi

if [[ "${vpn_openvpn}" == true ]]; then
    VPN_INTERFACE=$(cat "/var/run/openvpn/interface")
    bashio::log.info "VPN monitor set to query external IP through interface ${VPN_INTERFACE} (interface binding)."
elif [[ "${vpn_wireguard}" == true ]]; then
    VPN_INTERFACE=$(cat "/var/run/wireguard/interface")
    bashio::log.info "VPN monitor set to query external IP through interface ${VPN_INTERFACE} (interface binding)."
fi
if [[ -z "${VPN_INTERFACE}" ]] || ! ip link show "${VPN_INTERFACE}" > /dev/null 2>&1 ; then
    bashio::log.error "VPN interface not found."
    bashio::addon.stop
    exit 1
fi

REAL_IP="$(read_real_ip)"

if [[ -n "${REAL_IP}" ]]; then
    bashio::log.info "Real (non-VPN) IP from ${REAL_IP_FILE}: ${REAL_IP}"
else
    bashio::log.warning "Real IP file ${REAL_IP_FILE} missing or empty; IP leak detection will be less strict."
fi

bashio::log.info "VPN detected; enabling IP leak protection and periodic monitoring."

if ! VPN_INFO_OUT="$(wait_for_vpn_ip)"; then
    bashio::log.error "Unable to obtain a VPN external IP different from real IP. Stopping add-on."
    bashio::addon.stop
    exit 1
fi

read -r VPN_IP VPN_COUNTRY <<< "${VPN_INFO_OUT}"

bashio::log.info "VPN external IP: ${VPN_IP} (${VPN_COUNTRY})"

while true; do
    sleep "${VPN_CHECK_INTERVAL}" & wait $!

    if ! current_out="$(get_ip_info)"; then
        bashio::log.warning "Failed to refresh external IP; keeping previous assumptions."
        continue
    fi

    read -r current_ip current_country <<< "${current_out}"

    bashio::log.info "Current external IP: ${current_ip} (${current_country})"

    if [[ -n "${REAL_IP}" ]] && [[ "${current_ip}" == "${REAL_IP}" ]]; then
        bashio::log.error "IP LEAK DETECTED: current external IP ${current_ip} matches real IP ${REAL_IP}. Stopping add-on."
        bashio::addon.stop
        exit 1
    fi

    if [[ -z "${REAL_IP}" ]] && [[ "${current_ip}" == "${VPN_IP}" ]]; then
        # No baseline to compare; only report changes in IP for visibility
        VPN_IP="${current_ip}"
    fi

done
