package local

import (
	"context"
	"net"
	"net/netip"
	"os"
	"strconv"
	"syscall"
	"time"
	"unsafe"

	"github.com/sagernet/sing-box/adapter"
	"github.com/sagernet/sing/service"

	"golang.org/x/sys/windows"
)

func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
	conf := &dnsConfig{
		ndots:    1,
		timeout:  5 * time.Second,
		attempts: 2,
	}
	defer func() {
		if len(conf.servers) == 0 {
			conf.servers = defaultNS
		}
	}()
	addresses, err := adapterAddresses()
	if err != nil {
		return nil
	}
	var dnsAddresses []struct {
		ifName string
		netip.Addr
	}
	for _, address := range addresses {
		if address.OperStatus != windows.IfOperStatusUp {
			continue
		}
		if address.IfType == windows.IF_TYPE_TUNNEL {
			continue
		}
		if address.FirstGatewayAddress == nil {
			continue
		}
		for dnsServerAddress := address.FirstDnsServerAddress; dnsServerAddress != nil; dnsServerAddress = dnsServerAddress.Next {
			rawSockaddr, err := dnsServerAddress.Address.Sockaddr.Sockaddr()
			if err != nil {
				continue
			}
			var dnsServerAddr netip.Addr
			switch sockaddr := rawSockaddr.(type) {
			case *syscall.SockaddrInet4:
				dnsServerAddr = netip.AddrFrom4(sockaddr.Addr)
			case *syscall.SockaddrInet6:
				if sockaddr.Addr[0] == 0xfe && sockaddr.Addr[1] == 0xc0 {
					// fec0/10 IPv6 addresses are site local anycast DNS
					// addresses Microsoft sets by default if no other
					// IPv6 DNS address is set. Site local anycast is
					// deprecated since 2004, see
					// https://datatracker.ietf.org/doc/html/rfc3879
					continue
				}
				dnsServerAddr = netip.AddrFrom16(sockaddr.Addr)
				if sockaddr.ZoneId != 0 {
					dnsServerAddr = dnsServerAddr.WithZone(strconv.FormatInt(int64(sockaddr.ZoneId), 10))
				}
			default:
				// Unexpected type.
				continue
			}
			dnsAddresses = append(dnsAddresses, struct {
				ifName string
				netip.Addr
			}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
		}
	}
	var myInterface string
	if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
		myInterface = networkManager.InterfaceMonitor().MyInterface()
	}
	for _, address := range dnsAddresses {
		if address.ifName == myInterface {
			continue
		}
		conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
	}
	return conf
}

func adapterAddresses() ([]*windows.IpAdapterAddresses, error) {
	var b []byte
	l := uint32(15000) // recommended initial size
	for {
		b = make([]byte, l)
		const flags = windows.GAA_FLAG_INCLUDE_PREFIX | windows.GAA_FLAG_INCLUDE_GATEWAYS
		err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, flags, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l)
		if err == nil {
			if l == 0 {
				return nil, nil
			}
			break
		}
		if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW {
			return nil, os.NewSyscallError("getadaptersaddresses", err)
		}
		if l <= uint32(len(b)) {
			return nil, os.NewSyscallError("getadaptersaddresses", err)
		}
	}
	var aas []*windows.IpAdapterAddresses
	for aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next {
		aas = append(aas, aa)
	}
	return aas, nil
}
