/* SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
 */

package winipcfg

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net/netip"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

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

func runNetsh(cmds []string) error {
	system32, err := windows.GetSystemDirectory()
	if err != nil {
		return err
	}
	cmd := exec.Command(filepath.Join(system32, "netsh.exe"))
	cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return fmt.Errorf("runNetsh stdin pipe - %w", err)
	}
	go func() {
		defer stdin.Close()
		io.WriteString(stdin, strings.Join(append(cmds, "exit\r\n"), "\r\n"))
	}()
	output, err := cmd.CombinedOutput()
	// Horrible kludges, sorry.
	cleaned := bytes.ReplaceAll(output, []byte{'\r', '\n'}, []byte{'\n'})
	cleaned = bytes.ReplaceAll(cleaned, []byte("netsh>"), []byte{})
	cleaned = bytes.ReplaceAll(cleaned, []byte("There are no Domain Name Servers (DNS) configured on this computer."), []byte{})
	cleaned = bytes.TrimSpace(cleaned)
	if len(cleaned) != 0 && err == nil {
		return fmt.Errorf("netsh: %#q", string(cleaned))
	} else if err != nil {
		return fmt.Errorf("netsh: %v: %#q", err, string(cleaned))
	}
	return nil
}

const (
	netshCmdTemplateFlush4              = "interface ipv4 set dnsservers name=%d source=static address=none validate=no"
	netshCmdTemplateFlush6              = "interface ipv6 set dnsservers name=%d source=static address=none validate=no"
	netshCmdTemplateAdd4                = "interface ipv4 add dnsservers name=%d address=%s validate=no"
	netshCmdTemplateAdd6                = "interface ipv6 add dnsservers name=%d address=%s validate=no"
	netshCmdTemplateDisableRegistration = "interface ipv6 set dnsservers name=%d register=none"
)

func (luid LUID) fallbackSetDNSForFamily(family AddressFamily, dnses []netip.Addr) error {
	var templateFlush string
	if family == windows.AF_INET {
		templateFlush = netshCmdTemplateFlush4
	} else if family == windows.AF_INET6 {
		templateFlush = netshCmdTemplateFlush6
	}

	cmds := make([]string, 0, 1+len(dnses))
	ipif, err := luid.IPInterface(family)
	if err != nil {
		return err
	}
	cmds = append(cmds, fmt.Sprintf(templateFlush, ipif.InterfaceIndex))
	for i := 0; i < len(dnses); i++ {
		if dnses[i].Is4() && family == windows.AF_INET {
			cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif.InterfaceIndex, dnses[i].String()))
		} else if dnses[i].Is6() && family == windows.AF_INET6 {
			cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif.InterfaceIndex, dnses[i].String()))
		}
	}
	return runNetsh(cmds)
}

func (luid LUID) fallbackSetDNSDomain(domain string) error {
	guid, err := luid.GUID()
	if err != nil {
		return fmt.Errorf("Error converting luid to guid: %w", err)
	}
	key, err := registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Adapters\\%v", guid), registry.QUERY_VALUE)
	if err != nil {
		return fmt.Errorf("Error opening adapter-specific TCP/IP network registry key: %w", err)
	}
	paths, _, err := key.GetStringsValue("IpConfig")
	key.Close()
	if err != nil {
		return fmt.Errorf("Error reading IpConfig registry key: %w", err)
	}
	if len(paths) == 0 {
		return errors.New("No TCP/IP interfaces found on adapter")
	}
	key, err = registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\%s", paths[0]), registry.SET_VALUE)
	if err != nil {
		return fmt.Errorf("Unable to open TCP/IP network registry key: %w", err)
	}
	err = key.SetStringValue("Domain", domain)
	key.Close()
	return err
}

func (luid LUID) fallbackDisableDNSRegistration() error {
	// the DNS registration setting is shared for both IPv4 and IPv6
	ipif, err := luid.IPInterface(windows.AF_INET)
	if err != nil {
		return err
	}
	cmd := fmt.Sprintf(netshCmdTemplateDisableRegistration, ipif.InterfaceIndex)
	return runNetsh([]string{cmd})
}
