# redefine ip command to use FRR when it is available and preserve static addresses

# default route distance
IF_METRIC=${IF_METRIC:-210}

# Check if interface is inside a VRF
VRF_OPTION=$(/usr/sbin/ip --json --detail link show ${interface} | jq -r '.[0] | select(.linkinfo.info_slave_kind == "vrf") | "vrf \(.master)"')

# Flush only DHCP (dynamic) addresses from interface, preserving static addresses
#
#  When dhclient renews/rebinds a lease, it calls:
#      ip -4 addr flush dev <interface>
#  This removes ALL IPv4 addresses, including static addresses configured
#  via VyOS (e.g., "set interfaces ethernet eth0 address 192.168.1.1/24").
#
#  The ip() wrapper below intercepts the "ip -4 addr flush" command and replaces
#  it with a selective flush that only removes addresses marked as "dynamic" by
#  the kernel. DHCP-assigned addresses have the "dynamic" flag set automatically.
_flush_dhcp_addrs() {
    local dev="${1:-$interface}"

    logmsg info "Selectively flushing only dynamic (DHCP) addresses from ${dev}"

    local addrs
    addrs=$(/usr/sbin/ip -4 -j addr show dev "${dev}" 2>&1 | jq -r '
      .[] | .addr_info[]? | select(.family == "inet" and .dynamic == true)
      | "\(.local)/\(.prefixlen)"
    ' 2>&1)
    if [ $? -ne 0 ]; then
        logmsg warn "Failed to list dynamic IPv4 addresses on ${dev}; skipping selective flush. ip/jq error output: $addrs"
        return 0
    fi

    while IFS= read -r addr; do
        if [ -n "$addr" ]; then
            logmsg info "Removing dynamic address ${addr} from ${dev}"
            if ! /usr/sbin/ip -4 addr del "${addr}" dev "${dev}" 2>&1; then
                logmsg warn "Failed to remove dynamic address ${addr} from ${dev}"
            fi
        fi
    done <<< "$addrs"

    return 0
}

# get status of FRR
function frr_alive () {
    /usr/lib/frr/watchfrr.sh all_status
    if [ "$?" -eq "0" ] ; then
        logmsg info "FRR status: running"
        return 0
    else
        logmsg info "FRR status: not running"
        return 1
    fi
}

# convert ip route command to vtysh
function iptovtysh () {
    # prepare variables for vtysh command
    local VTYSH_ACTION=$3
    local VTYSH_NETADDR=""
    local VTYSH_GATEWAY=""
    local VTYSH_DEV=""
    local VTYSH_TAG="210"
    local VTYSH_DISTANCE=$IF_METRIC
    # convert default route to 0.0.0.0/0
    if [ "$4" == "default" ] ; then
        VTYSH_NETADDR="0.0.0.0/0"
    else
        VTYSH_NETADDR=$4
    fi
    # add /32 to ip addresses without netmasks
    if [[ ! $VTYSH_NETADDR =~ ^.*/[[:digit:]]+$ ]] ; then
        VTYSH_NETADDR="$VTYSH_NETADDR/32"
    fi
    shift 4
    # get gateway address
    if [ "$1" == "via" ] ; then
        VTYSH_GATEWAY=$2
        shift 2
    fi
    # get device name
    if [ "$1" == "dev" ]; then
        VTYSH_DEV=$2
        shift 2
    fi
    # get distance
    if [ "$1" == "metric" ]; then
        VTYSH_DISTANCE=$2
        shift 2
    fi

    VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE $VRF_OPTION"

    # delete route if the command is "del"
    if [ "$VTYSH_ACTION" == "del" ] ; then
        VTYSH_CMD="no $VTYSH_CMD"
    fi
    logmsg info "Converted vtysh command: \"$VTYSH_CMD\""
}

# delete the same route from kernel before adding new one
function delroute () {
    logmsg info "Checking if the route presented in kernel: $@ $VRF_OPTION"
    if /usr/sbin/ip route show $@ $VRF_OPTION | grep -qx "$1 " ; then
        logmsg info "Deleting IP route: \"/usr/sbin/ip route del $@ $VRF_OPTION\""
        /usr/sbin/ip route del $@ $VRF_OPTION
    fi
}

# try to communicate with vtysh
function vtysh_conf () {
    # perform 10 attempts with 1 second delay for retries
    for i in {1..10} ; do
        if vtysh  -c "conf t" -c "$1" ; then
            logmsg info "Command was executed successfully via vtysh: \"$1\""
            return 0
        else
            logmsg info "Failed to send command to vtysh, retrying in 1 second"
            sleep 1
        fi
    done
    logmsg error "Failed to execute command via vtysh after 10 attempts: \"$1\""
    return 1
}

# replace ip command with this wrapper
function ip () {
    # Intercept: ip -4 addr flush dev <interface>
    # to preserve static addresses (only remove dynamic/DHCP addresses)
    if [ "$1" = "-4" ] && [ "$2" = "addr" ] && [ "$3" = "flush" ]; then
        logmsg info "Intercepting 'ip -4 addr flush' to preserve static addresses"
        shift 3
        local dev=""
        while [ $# -gt 0 ]; do
            [ "$1" = "dev" ] && dev="$2" && break
            shift
        done
        _flush_dhcp_addrs "${dev:-$interface}"
        return $?
    fi

    # pass command to system `ip` if this is not related to routes change
    if [ "$2" != "route" ] ; then
        logmsg info "Passing command to /usr/sbin/ip: \"$@\""
        /usr/sbin/ip $@
    else
        # if we want to work with routes, try to use FRR first
        if frr_alive ; then
            delroute ${@:4}
            iptovtysh $@
            logmsg info "Sending command to vtysh"
            vtysh_conf "$VTYSH_CMD"
        else
            # add ip route to kernel
            logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\""
            /usr/sbin/ip $@ $VRF_OPTION
        fi
    fi
}
