//go:build darwin

package platform

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

// DarwinNetworkDiscoverer discovers network configuration using route(8) and
// scutil(8). Both commands are available on every macOS version since 10.5.
type DarwinNetworkDiscoverer struct{}

// Discover returns the default gateway IP and DNS server list for this host.
func (d *DarwinNetworkDiscoverer) Discover() (NetworkInfo, error) {
	info := NetworkInfo{}

	// Step 1: Determine default gateway and interface via "route -n get default".
	gateway, iface, err := darwinDefaultRoute()
	if err != nil {
		return info, fmt.Errorf("reading default route: %w", err)
	}
	info.Gateway = gateway
	info.Interface = iface

	// Step 2: Extract DNS servers from scutil --dns, matching the interface.
	servers, err := darwinDNSServers(iface)
	if err != nil {
		// Non-fatal — return what we have.
		return info, nil
	}
	info.DNSServers = servers

	return info, nil
}

// darwinDefaultRoute runs "route -n get default" and returns the gateway IP
// and interface name for the default route.
func darwinDefaultRoute() (gateway, iface string, err error) {
	out, err := exec.Command("route", "-n", "get", "default").Output()
	if err != nil {
		return "", "", fmt.Errorf("route -n get default: %w", err)
	}

	scanner := bufio.NewScanner(strings.NewReader(string(out)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "gateway:") {
			fields := strings.Fields(line)
			if len(fields) >= 2 {
				ip := fields[1]
				if net.ParseIP(ip) != nil {
					gateway = ip
				}
			}
		}
		if strings.HasPrefix(line, "interface:") {
			fields := strings.Fields(line)
			if len(fields) >= 2 {
				iface = fields[1]
			}
		}
	}

	if gateway == "" {
		return "", "", fmt.Errorf("no default gateway found")
	}
	return gateway, iface, nil
}

// darwinDNSServers runs "scutil --dns" and extracts DNS server IPs from the
// resolver block that matches iface. Falls back to resolver #1 if no match.
func darwinDNSServers(iface string) ([]string, error) {
	out, err := exec.Command("scutil", "--dns").Output()
	if err != nil {
		return nil, fmt.Errorf("scutil --dns: %w", err)
	}

	type resolverBlock struct {
		nameservers []string
		ifaceMatch  bool
		isMDNS      bool
		number      int
	}

	var blocks []resolverBlock
	var current *resolverBlock
	inScopedSection := false

	scanner := bufio.NewScanner(strings.NewReader(string(out)))
	for scanner.Scan() {
		line := scanner.Text()
		stripped := strings.TrimSpace(line)

		// Detect the "scoped queries" section — stop parsing there.
		if strings.Contains(line, "DNS configuration (for scoped queries)") {
			inScopedSection = true
		}
		if inScopedSection {
			continue
		}

		// Start of a new resolver block.
		if strings.HasPrefix(stripped, "resolver #") {
			if current != nil {
				blocks = append(blocks, *current)
			}
			num := 0
			fmt.Sscanf(stripped, "resolver #%d", &num)
			current = &resolverBlock{number: num}
			continue
		}

		if current == nil {
			continue
		}

		// Parse nameserver entries.
		if strings.HasPrefix(stripped, "nameserver[") {
			// Format: "nameserver[0] : 8.8.8.8"
			idx := strings.Index(stripped, ": ")
			if idx >= 0 {
				ip := strings.TrimSpace(stripped[idx+2:])
				if net.ParseIP(ip) != nil {
					current.nameservers = append(current.nameservers, ip)
				}
			}
			continue
		}

		// Check for interface match via if_index line: "if_index : 5 (en0)"
		if strings.HasPrefix(stripped, "if_index") && iface != "" {
			if strings.Contains(stripped, "("+iface+")") {
				current.ifaceMatch = true
			}
			continue
		}

		// Detect mDNS blocks: domain + options lines.
		if strings.HasPrefix(stripped, "domain") && strings.Contains(stripped, ": local") {
			current.isMDNS = true
		}
		if strings.HasPrefix(stripped, "options") && strings.Contains(stripped, "mdns") {
			current.isMDNS = true
		}
	}
	if current != nil {
		blocks = append(blocks, *current)
	}

	// Find the best block: interface-matching, non-mDNS.
	for _, b := range blocks {
		if b.ifaceMatch && !b.isMDNS && len(b.nameservers) > 0 {
			return b.nameservers, nil
		}
	}

	// Fallback: use resolver #1 if it's not mDNS.
	for _, b := range blocks {
		if b.number == 1 && !b.isMDNS && len(b.nameservers) > 0 {
			return b.nameservers, nil
		}
	}

	return nil, fmt.Errorf("no usable DNS resolver block found in scutil output")
}
