package aghnet

import (
	"fmt"
	"net"
	"time"

	"github.com/AdguardTeam/golibs/log"
)

// IPVersion is a alias for int for documentation purposes.  Use it when the
// integer means IP version.
type IPVersion = int

// IP version constants.
const (
	IPVersion4 IPVersion = 4
	IPVersion6 IPVersion = 6
)

// NetIface is the interface for network interface methods.
type NetIface interface {
	Addrs() ([]net.Addr, error)
}

// IfaceIPAddrs returns the interface's IP addresses.
func IfaceIPAddrs(iface NetIface, ipv IPVersion) (ips []net.IP, err error) {
	addrs, err := iface.Addrs()
	if err != nil {
		return nil, err
	}

	for _, a := range addrs {
		var ip net.IP
		switch a := a.(type) {
		case *net.IPAddr:
			ip = a.IP
		case *net.IPNet:
			ip = a.IP
		default:
			continue
		}

		// Assume that net.(*Interface).Addrs can only return valid IPv4
		// and IPv6 addresses.  Thus, if it isn't an IPv4 address, it
		// must be an IPv6 one.
		switch ipv {
		case IPVersion4:
			if ip4 := ip.To4(); ip4 != nil {
				ips = append(ips, ip4)
			}
		case IPVersion6:
			if ip6 := ip.To4(); ip6 == nil {
				ips = append(ips, ip)
			}
		default:
			return nil, fmt.Errorf("invalid ip version %d", ipv)
		}
	}

	return ips, nil
}

// IfaceDNSIPAddrs returns IP addresses of the interface suitable to send to
// clients as DNS addresses.  If err is nil, addrs contains either no addresses
// or at least two.
//
// It makes up to maxAttempts attempts to get the addresses if there are none,
// each time using the provided backoff.  Sometimes an interface needs a few
// seconds to really initialize.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2304.
func IfaceDNSIPAddrs(
	iface NetIface,
	ipv IPVersion,
	maxAttempts int,
	backoff time.Duration,
) (addrs []net.IP, err error) {
	var n int
	for n = 1; n <= maxAttempts; n++ {
		addrs, err = IfaceIPAddrs(iface, ipv)
		if err != nil {
			return nil, fmt.Errorf("getting ip addrs: %w", err)
		}

		if len(addrs) > 0 {
			break
		}

		log.Debug("dhcpv%d: attempt %d: no ip addresses", ipv, n)

		time.Sleep(backoff)
	}

	n--

	switch len(addrs) {
	case 0:
		// Don't return errors in case the users want to try and enable
		// the DHCP server later.
		t := time.Duration(n) * backoff
		log.Error("dhcpv%d: no ip for iface after %d attempts and %s", ipv, n, t)

		return nil, nil
	case 1:
		// Some Android devices use 8.8.8.8 if there is not a secondary
		// DNS server.  Fix that by setting the secondary DNS address to
		// the same address.
		//
		// See https://github.com/AdguardTeam/AdGuardHome/issues/1708.
		log.Debug("dhcpv%d: setting secondary dns ip to itself", ipv)
		addrs = append(addrs, addrs[0])
	default:
		// Go on.
	}

	log.Debug("dhcpv%d: got addresses %s after %d attempts", ipv, addrs, n)

	return addrs, nil
}

// interfaceName is a string containing network interface's name.  The name is
// used in file walking methods.
type interfaceName string

// Use interfaceName in the OS-independent code since it's actually only used in
// several OS-dependent implementations which causes linting issues.
var _ = interfaceName("")