//go:build windows

package platform

import (
	"bufio"
	"fmt"
	"net"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
)

// WindowsNetworkDiscoverer discovers network configuration using netsh commands.
// It does not require elevated privileges or PowerShell.
type WindowsNetworkDiscoverer struct{}

var ipv4Re = regexp.MustCompile(`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`)
var dnsLineRe = regexp.MustCompile(`^\s*(?:\S.*:\s+)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*$`)

// Discover returns the gateway IP and DNS servers for the default IPv4 route.
func (d *WindowsNetworkDiscoverer) Discover() (NetworkInfo, error) {
	info := NetworkInfo{}

	// Step 1: Find default route — extract interface index and gateway IP.
	ifIdx, gateway, err := windowsDefaultRoute()
	if err != nil {
		return info, fmt.Errorf("reading default IPv4 route: %w", err)
	}
	info.Gateway = gateway

	// Step 2: Map interface index to name.
	ifName, err := windowsInterfaceName(ifIdx)
	if err != nil {
		return info, fmt.Errorf("resolving interface name for index %d: %w", ifIdx, err)
	}
	info.Interface = ifName

	// Step 3: Enumerate DNS servers for that interface.
	servers, err := windowsDNSServers(ifName)
	if err != nil {
		return info, fmt.Errorf("reading DNS servers for interface %q: %w", ifName, err)
	}
	info.DNSServers = servers

	return info, nil
}

// windowsDefaultRoute runs "netsh interface ipv4 show route" and returns the
// interface index and gateway IP for the best (lowest-metric) 0.0.0.0/0 route.
func windowsDefaultRoute() (ifIdx int, gateway string, err error) {
	out, err := exec.Command("netsh", "interface", "ipv4", "show", "route").Output()
	if err != nil {
		return 0, "", fmt.Errorf("netsh interface ipv4 show route: %w", err)
	}

	bestMetric := -1
	bestIdx := 0
	bestGateway := ""

	scanner := bufio.NewScanner(strings.NewReader(string(out)))
	for scanner.Scan() {
		line := scanner.Text()
		if !strings.Contains(line, "0.0.0.0/0") {
			continue
		}
		// Format: <Publish> <Type> <Metric> <Prefix> <Idx> <Gateway or Iface Name...>
		fields := strings.Fields(line)
		if len(fields) < 6 {
			continue
		}
		metric, err := strconv.Atoi(fields[2])
		if err != nil {
			continue
		}
		idx, err := strconv.Atoi(fields[4])
		if err != nil {
			continue
		}
		// Gateway field is field 5+ — join and extract first IPv4.
		gatewayField := strings.Join(fields[5:], " ")
		m := ipv4Re.FindString(gatewayField)
		if m == "" {
			continue
		}
		if bestMetric < 0 || metric < bestMetric {
			bestMetric = metric
			bestIdx = idx
			bestGateway = m
		}
	}

	if bestIdx == 0 {
		return 0, "", fmt.Errorf("no default IPv4 route found")
	}
	return bestIdx, bestGateway, nil
}

// windowsInterfaceName runs "netsh interface ipv4 show interfaces" and maps
// the given interface index to the interface name.
func windowsInterfaceName(idx int) (string, error) {
	out, err := exec.Command("netsh", "interface", "ipv4", "show", "interfaces").Output()
	if err != nil {
		return "", fmt.Errorf("netsh interface ipv4 show interfaces: %w", err)
	}

	idxStr := strconv.Itoa(idx)
	scanner := bufio.NewScanner(strings.NewReader(string(out)))
	// Skip two header lines.
	for i := 0; i < 2; i++ {
		scanner.Scan()
	}
	for scanner.Scan() {
		line := scanner.Text()
		fields := strings.Fields(line)
		if len(fields) < 5 {
			continue
		}
		if fields[0] != idxStr {
			continue
		}
		// Name is fields[4:] joined — may contain spaces.
		return strings.Join(fields[4:], " "), nil
	}
	return "", fmt.Errorf("interface index %d not found", idx)
}

// windowsDNSServers runs "netsh interface ipv4 show dnsservers name=<ifName>"
// and returns all IPv4 DNS server addresses in order.
func windowsDNSServers(ifName string) ([]string, error) {
	out, err := exec.Command("netsh", "interface", "ipv4", "show", "dnsservers",
		"name="+ifName).Output()
	if err != nil {
		return nil, fmt.Errorf("netsh interface ipv4 show dnsservers: %w", err)
	}

	var servers []string
	scanner := bufio.NewScanner(strings.NewReader(string(out)))
	for scanner.Scan() {
		m := dnsLineRe.FindStringSubmatch(scanner.Text())
		if len(m) < 2 {
			continue
		}
		ip := m[1]
		if net.ParseIP(ip) != nil {
			servers = append(servers, ip)
		}
	}
	return servers, nil
}
