// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package ice

import (
	"net"
	"net/netip"

	"github.com/pion/ice/v4/internal/netutil"
	"github.com/pion/logging"
	"github.com/pion/transport/v4"
)

type ifaceAddr struct {
	addr  netip.Addr
	iface string
}

// The conditions of invalidation written below are defined in
// https://tools.ietf.org/html/rfc8445#section-5.1.1.1
// It is partial because the link-local check is done later in various gather local
// candidate methods which conditionally accept IPv6 based on usage of mDNS or not.
func isSupportedIPv6Partial(ip net.IP) bool {
	if len(ip) != net.IPv6len ||
		// Deprecated IPv4-compatible IPv6 addresses [RFC4291] and IPv6 site-
		//   local unicast addresses [RFC3879] MUST NOT be included in the
		//   address candidates.
		isZeros(ip[0:12]) || // !(IPv4-compatible IPv6)
		ip[0] == 0xfe && ip[1]&0xc0 == 0xc0 { // !(IPv6 site-local unicast)
		return false
	}

	return true
}

func isZeros(ip net.IP) bool {
	for i := range ip {
		if ip[i] != 0 {
			return false
		}
	}

	return true
}

//nolint:gocognit,cyclop
func localInterfaces(
	n transport.Net,
	interfaceFilter func(string) (keep bool),
	ipFilter func(net.IP) (keep bool),
	networkTypes []NetworkType,
	includeLoopback bool,
) ([]*transport.Interface, []ifaceAddr, error) {
	ipAddrs := []ifaceAddr{}
	ifaces, err := n.Interfaces()
	if err != nil {
		return nil, ipAddrs, err
	}

	filteredIfaces := make([]*transport.Interface, 0, len(ifaces))

	var ipV4Requested, ipv6Requested bool
	if len(networkTypes) == 0 {
		ipV4Requested = true
		ipv6Requested = true
	} else {
		for _, typ := range networkTypes {
			if typ.IsIPv4() {
				ipV4Requested = true
			}

			if typ.IsIPv6() {
				ipv6Requested = true
			}
		}
	}

	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // Interface down
		}
		if (iface.Flags&net.FlagLoopback != 0) && !includeLoopback {
			continue // Loopback interface
		}

		if interfaceFilter != nil && !interfaceFilter(iface.Name) {
			continue
		}

		ifaceAddrs, err := iface.Addrs()
		if err != nil {
			continue
		}

		atLeastOneAddr := false
		for _, addr := range ifaceAddrs {
			ipAddr, _, _, err := parseAddrFromIface(addr, iface.Name)
			if err != nil || (ipAddr.IsLoopback() && !includeLoopback) {
				continue
			}
			if ipAddr.Is6() {
				if !ipv6Requested {
					continue
				} else if !isSupportedIPv6Partial(ipAddr.AsSlice()) {
					continue
				}
			} else if !ipV4Requested {
				continue
			}

			if ipFilter != nil && !ipFilter(ipAddr.AsSlice()) {
				continue
			}

			atLeastOneAddr = true
			ipAddrs = append(ipAddrs, ifaceAddr{addr: ipAddr, iface: iface.Name})
		}

		if atLeastOneAddr {
			ifaceCopy := iface
			filteredIfaces = append(filteredIfaces, ifaceCopy)
		}
	}

	return filteredIfaces, ipAddrs, nil
}

//nolint:cyclop
func listenUDPInPortRange(
	netTransport transport.Net,
	log logging.LeveledLogger,
	portMax, portMin int,
	network string,
	lAddr *net.UDPAddr,
) (transport.UDPConn, error) {
	if (lAddr.Port != 0) || ((portMin == 0) && (portMax == 0)) {
		return netTransport.ListenUDP(network, lAddr)
	}

	if portMin == 0 {
		portMin = 1024 // Start at 1024 which is non-privileged
	}

	if portMax == 0 {
		portMax = 0xFFFF
	}

	if portMin > portMax {
		return nil, ErrPort
	}

	portStart := globalMathRandomGenerator.Intn(portMax-portMin+1) + portMin
	portCurrent := portStart
	for {
		addr := &net.UDPAddr{
			IP:   lAddr.IP,
			Zone: lAddr.Zone,
			Port: portCurrent,
		}

		c, e := netTransport.ListenUDP(network, addr)
		if e == nil {
			return c, e //nolint:nilerr
		}
		log.Debugf("Failed to listen %s: %v", lAddr.String(), e)
		if netutil.IsAddrUnavailable(e) {
			return nil, e
		}
		portCurrent++
		if portCurrent > portMax {
			portCurrent = portMin
		}
		if portCurrent == portStart {
			break
		}
	}

	return nil, ErrPort
}
