//go:build (linux && !android) || freebsd

package dns

import (
	"bytes"
	"fmt"
	"net/netip"
	"os/exec"
	"strings"
)

const resolvconfCommand = "resolvconf"

// ResolvconfDNSConfigurator manages DNS settings using the resolvconf utility
type ResolvconfDNSConfigurator struct {
	ifaceName     string
	implType      string
	originalState *DNSState
}

// NewResolvconfDNSConfigurator creates a new resolvconf DNS configurator
func NewResolvconfDNSConfigurator(ifaceName string) (*ResolvconfDNSConfigurator, error) {
	if ifaceName == "" {
		return nil, fmt.Errorf("interface name is required")
	}

	// Detect resolvconf implementation type
	implType, err := detectResolvconfType()
	if err != nil {
		return nil, fmt.Errorf("detect resolvconf type: %w", err)
	}

	return &ResolvconfDNSConfigurator{
		ifaceName: ifaceName,
		implType:  implType,
	}, nil
}

// Name returns the configurator name
func (r *ResolvconfDNSConfigurator) Name() string {
	return fmt.Sprintf("resolvconf-%s", r.implType)
}

// SetDNS sets the DNS servers and returns the original servers
func (r *ResolvconfDNSConfigurator) SetDNS(servers []netip.Addr) ([]netip.Addr, error) {
	// Get current DNS settings before overriding
	originalServers, err := r.GetCurrentDNS()
	if err != nil {
		// If we can't get current DNS, proceed anyway
		originalServers = []netip.Addr{}
	}

	// Store original state
	r.originalState = &DNSState{
		OriginalServers:  originalServers,
		ConfiguratorName: r.Name(),
	}

	// Apply new DNS servers
	if err := r.applyDNSServers(servers); err != nil {
		return nil, fmt.Errorf("apply DNS servers: %w", err)
	}

	return originalServers, nil
}

// RestoreDNS restores the original DNS configuration
func (r *ResolvconfDNSConfigurator) RestoreDNS() error {
	var cmd *exec.Cmd

	switch r.implType {
	case "openresolv":
		// Force delete with -f
		cmd = exec.Command(resolvconfCommand, "-f", "-d", r.ifaceName)
	default:
		cmd = exec.Command(resolvconfCommand, "-d", r.ifaceName)
	}

	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("delete resolvconf config: %w, output: %s", err, out)
	}

	return nil
}

// GetCurrentDNS returns the currently configured DNS servers
func (r *ResolvconfDNSConfigurator) GetCurrentDNS() ([]netip.Addr, error) {
	// resolvconf doesn't provide a direct way to query per-interface DNS
	// We can try to read /etc/resolv.conf but it's merged from all sources
	content, err := exec.Command(resolvconfCommand, "-l").CombinedOutput()
	if err != nil {
		// Fall back to reading resolv.conf
		return readResolvConfServers()
	}

	// Parse the output (format varies by implementation)
	return parseResolvconfOutput(string(content)), nil
}

// applyDNSServers applies DNS server configuration via resolvconf
func (r *ResolvconfDNSConfigurator) applyDNSServers(servers []netip.Addr) error {
	if len(servers) == 0 {
		return fmt.Errorf("no DNS servers provided")
	}

	// Build resolv.conf content
	var content bytes.Buffer
	content.WriteString("# Generated by Olm DNS Manager\n\n")

	for _, server := range servers {
		content.WriteString("nameserver ")
		content.WriteString(server.String())
		content.WriteString("\n")
	}

	// Apply via resolvconf
	var cmd *exec.Cmd
	switch r.implType {
	case "openresolv":
		// OpenResolv supports exclusive mode with -x
		cmd = exec.Command(resolvconfCommand, "-x", "-a", r.ifaceName)
	default:
		cmd = exec.Command(resolvconfCommand, "-a", r.ifaceName)
	}

	cmd.Stdin = &content
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("apply resolvconf config: %w, output: %s", err, out)
	}

	return nil
}

// detectResolvconfType detects which resolvconf implementation is being used
func detectResolvconfType() (string, error) {
	cmd := exec.Command(resolvconfCommand, "--version")
	out, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("detect resolvconf type: %w", err)
	}

	if strings.Contains(string(out), "openresolv") {
		return "openresolv", nil
	}

	return "resolvconf", nil
}

// parseResolvconfOutput parses resolvconf -l output for DNS servers
func parseResolvconfOutput(output string) []netip.Addr {
	var servers []netip.Addr

	lines := strings.Split(output, "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)

		// Skip comments and empty lines
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		// Look for nameserver lines
		if strings.HasPrefix(line, "nameserver") {
			fields := strings.Fields(line)
			if len(fields) >= 2 {
				if addr, err := netip.ParseAddr(fields[1]); err == nil {
					servers = append(servers, addr)
				}
			}
		}
	}

	return servers
}

// readResolvConfServers reads DNS servers from /etc/resolv.conf
func readResolvConfServers() ([]netip.Addr, error) {
	cmd := exec.Command("cat", "/etc/resolv.conf")
	out, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("read resolv.conf: %w", err)
	}

	return parseResolvconfOutput(string(out)), nil
}

// IsResolvconfAvailable checks if resolvconf is available
func IsResolvconfAvailable() bool {
	cmd := exec.Command(resolvconfCommand, "--version")
	return cmd.Run() == nil
}
