//go:build windows

package libnetwork

import (
	"context"
	"fmt"
	"net"
	"net/netip"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/Microsoft/hcsshim"
	"github.com/containerd/log"
	"github.com/docker/docker/libnetwork/drivers/windows"
	"github.com/docker/docker/libnetwork/ipamapi"
	"github.com/docker/docker/libnetwork/ipams/windowsipam"
	"github.com/pkg/errors"
)

type platformNetwork struct {
	resolverOnce   sync.Once
	dnsCompartment uint32
}

func executeInCompartment(compartmentID uint32, x func()) {
	runtime.LockOSThread()

	if err := hcsshim.SetCurrentThreadCompartmentId(compartmentID); err != nil {
		log.G(context.TODO()).Error(err)
	}
	defer func() {
		hcsshim.SetCurrentThreadCompartmentId(0)
		runtime.UnlockOSThread()
	}()

	x()
}

func (n *Network) ExecFunc(f func()) error {
	executeInCompartment(n.dnsCompartment, f)
	return nil
}

func (n *Network) startResolver() {
	if n.networkType == "ics" {
		return
	}
	n.resolverOnce.Do(func() {
		log.G(context.TODO()).Debugf("Launching DNS server for network %q", n.Name())
		hnsid := n.DriverOptions()[windows.HNSID]
		if hnsid == "" {
			return
		}

		hnsresponse, err := hcsshim.HNSNetworkRequest("GET", hnsid, "")
		if err != nil {
			log.G(context.TODO()).Errorf("Resolver Setup/Start failed for container %s, %q", n.Name(), err)
			return
		}

		for _, subnet := range hnsresponse.Subnets {
			if subnet.GatewayAddress != "" {
				for i := 0; i < 3; i++ {
					resolver := NewResolver(subnet.GatewayAddress, true, n)
					log.G(context.TODO()).Debugf("Binding a resolver on network %s gateway %s", n.Name(), subnet.GatewayAddress)
					n.dnsCompartment = hnsresponse.DNSServerCompartment
					n.ExecFunc(resolver.SetupFunc(53))

					if err = resolver.Start(); err != nil {
						log.G(context.TODO()).Errorf("Resolver Setup/Start failed for container %s, %q", n.Name(), err)
						time.Sleep(1 * time.Second)
					} else {
						log.G(context.TODO()).Debugf("Resolver bound successfully for network %s", n.Name())
						n.resolver = append(n.resolver, resolver)
						break
					}
				}
			}
		}
	})
}

// addEpToResolver configures the internal DNS resolver for an endpoint.
//
// Windows resolvers don't consistently fall back to a secondary server if they
// get a SERVFAIL from our resolver. So, our resolver needs to forward the query
// upstream.
//
// To retrieve the list of DNS Servers to use for requests originating from an
// endpoint, this method finds the HNSEndpoint represented by the endpoint. If
// HNSEndpoint's list of DNS servers includes the HNSEndpoint's gateway address,
// it's the Resolver running at that address. Other DNS servers in the
// list have either come from config ('--dns') or have been set up by HNS as
// external resolvers, these are the external servers the Resolver should
// use for DNS requests from that endpoint.
func addEpToResolver(
	ctx context.Context,
	netName, epName string,
	config *containerConfig,
	epIface *EndpointInterface,
	resolvers []*Resolver,
) error {
	if config.dnsNoProxy {
		return nil
	}
	hnsEndpoints, err := hcsshim.HNSListEndpointRequest()
	if err != nil {
		return nil
	}
	return addEpToResolverImpl(ctx, netName, epName, epIface, resolvers, hnsEndpoints)
}

func addEpToResolverImpl(
	ctx context.Context,
	netName, epName string,
	epIface *EndpointInterface,
	resolvers []*Resolver,
	hnsEndpoints []hcsshim.HNSEndpoint,
) error {
	// Find the HNSEndpoint represented by ep, matching on endpoint address.
	hnsEp := findHNSEp(epIface.addr, epIface.addrv6, hnsEndpoints)
	if hnsEp == nil || !hnsEp.EnableInternalDNS {
		return nil
	}

	// Find the resolver for that HNSEndpoint, matching on gateway address.
	resolver := findResolver(resolvers, hnsEp.GatewayAddress, hnsEp.GatewayAddressV6)
	if resolver == nil {
		log.G(ctx).WithFields(log.Fields{
			"network":  netName,
			"endpoint": epName,
		}).Debug("No internal DNS resolver to configure")
		return nil
	}

	// Get the list of DNS servers HNS has set up for this Endpoint.
	var dnsList []extDNSEntry
	dnsServers := strings.Split(hnsEp.DNSServerList, ",")

	// Create an extDNSEntry for each DNS server, apart from 'resolver' itself.
	var foundSelf bool
	hnsGw4, _ := netip.ParseAddr(hnsEp.GatewayAddress)
	hnsGw6, _ := netip.ParseAddr(hnsEp.GatewayAddressV6)
	for _, dnsServer := range dnsServers {
		dnsAddr, _ := netip.ParseAddr(dnsServer)
		if dnsAddr.IsValid() && (dnsAddr == hnsGw4 || dnsAddr == hnsGw6) {
			foundSelf = true
		} else {
			dnsList = append(dnsList, extDNSEntry{IPStr: dnsServer})
		}
	}
	if !foundSelf {
		log.G(ctx).WithFields(log.Fields{
			"network":  netName,
			"endpoint": epName,
		}).Debug("Endpoint is not configured to use internal DNS resolver")
		return nil
	}

	// If the internal resolver is configured as one of this endpoint's DNS servers,
	// tell it which ext servers to use for requests from this endpoint's addresses.
	log.G(ctx).Infof("External DNS servers for '%s': %v", epName, dnsList)
	if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPAddress); ok {
		if err := resolver.SetExtServersForSrc(srcAddr.Unmap(), dnsList); err != nil {
			return errors.Wrapf(err, "failed to set external DNS servers for %s address %s",
				epName, hnsEp.IPAddress)
		}
	}
	if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPv6Address); ok {
		if err := resolver.SetExtServersForSrc(srcAddr, dnsList); err != nil {
			return errors.Wrapf(err, "failed to set external DNS servers for %s address %s",
				epName, hnsEp.IPv6Address)
		}
	}
	return nil
}

func deleteEpFromResolver(epName string, epIface *EndpointInterface, resolvers []*Resolver) error {
	hnsEndpoints, err := hcsshim.HNSListEndpointRequest()
	if err != nil {
		return nil
	}
	return deleteEpFromResolverImpl(epName, epIface, resolvers, hnsEndpoints)
}

func deleteEpFromResolverImpl(
	epName string,
	epIface *EndpointInterface,
	resolvers []*Resolver,
	hnsEndpoints []hcsshim.HNSEndpoint,
) error {
	// Find the HNSEndpoint represented by ep, matching on endpoint address.
	hnsEp := findHNSEp(epIface.addr, epIface.addrv6, hnsEndpoints)
	if hnsEp == nil {
		return fmt.Errorf("no HNS endpoint for %s", epName)
	}

	// Find the resolver for that HNSEndpoint, matching on gateway address.
	resolver := findResolver(resolvers, hnsEp.GatewayAddress, hnsEp.GatewayAddressV6)
	if resolver == nil {
		return nil
	}

	// Delete external DNS servers for the endpoint's IP addresses.
	if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPAddress); ok {
		if err := resolver.SetExtServersForSrc(srcAddr.Unmap(), nil); err != nil {
			return errors.Wrapf(err, "failed to delete external DNS servers for %s address %s",
				epName, hnsEp.IPv6Address)
		}
	}
	if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPv6Address); ok {
		if err := resolver.SetExtServersForSrc(srcAddr, nil); err != nil {
			return errors.Wrapf(err, "failed to delete external DNS servers for %s address %s",
				epName, hnsEp.IPv6Address)
		}
	}

	return nil
}

func findHNSEp(ip4, ip6 *net.IPNet, hnsEndpoints []hcsshim.HNSEndpoint) *hcsshim.HNSEndpoint {
	for _, hnsEp := range hnsEndpoints {
		if (hnsEp.IPAddress != nil && hnsEp.IPAddress.Equal(ip4.IP)) ||
			(hnsEp.IPv6Address != nil && hnsEp.IPv6Address.Equal(ip6.IP)) {
			return &hnsEp
		}
	}
	return nil
}

func findResolver(resolvers []*Resolver, gw4, gw6 string) *Resolver {
	gw4addr, _ := netip.ParseAddr(gw4)
	gw6addr, _ := netip.ParseAddr(gw6)
	for _, resolver := range resolvers {
		ns := resolver.NameServer()
		if ns.IsValid() && (ns == gw4addr || ns == gw6addr) {
			return resolver
		}
	}
	return nil
}

func defaultIpamForNetworkType(networkType string) string {
	if windows.IsBuiltinLocalDriver(networkType) {
		return windowsipam.DefaultIPAM
	}
	return ipamapi.DefaultIPAM
}
