package routing

import (
	"errors"
	"fmt"
	"net/netip"

	"github.com/qdm12/gluetun/internal/netlink"
	"golang.org/x/sys/unix"
)

var (
	ErrVPNLocalGatewayIPNotFound       = errors.New("VPN local gateway IP address not found")
	ErrVPNLocalGatewayIPv6NotSupported = errors.New("VPN local gateway IPv6 address not supported")
)

func (r *Routing) VPNLocalGatewayIP(vpnIntf string) (ip netip.Addr, err error) {
	vpnLink, err := r.netLinker.LinkByName(vpnIntf)
	if err != nil {
		return ip, fmt.Errorf("finding link %s: %w", vpnIntf, err)
	}
	vpnLinkIndex := vpnLink.Index

	routes, err := r.netLinker.RouteList(netlink.FamilyAll)
	if err != nil {
		return ip, fmt.Errorf("listing routes: %w", err)
	}
	for _, route := range routes {
		if route.LinkIndex != vpnLinkIndex {
			continue
		}

		switch {
		case route.Dst.IsValid() && route.Dst.Addr().IsUnspecified(): // OpenVPN
			return route.Gw, nil
		case route.Dst.IsSingleIP() &&
			route.Dst.Addr().Compare(route.Src) == 0 &&
			route.Table == unix.RT_TABLE_LOCAL: // Wireguard
			route.Src = route.Src.Unmap()
			if route.Src.Is6() {
				return netip.Addr{}, fmt.Errorf("%w: %s", ErrVPNLocalGatewayIPv6NotSupported, route.Src)
			}
			bytes := route.Src.As4()
			// force last byte to 1 to get the VPN gateway IP
			// This is not necessarily bullet proof but it seems to work.
			bytes[3] = 1
			return netip.AddrFrom4(bytes), nil
		}
	}
	return ip, fmt.Errorf("%w: in %d routes", ErrVPNLocalGatewayIPNotFound, len(routes))
}
