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

package dns

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

const (
	resolvConfPath       = "/etc/resolv.conf"
	resolvConfBackupPath = "/etc/resolv.conf.olm.backup"
	resolvConfHeader     = "# Generated by Olm DNS Manager\n# Original file backed up to " + resolvConfBackupPath + "\n\n"
)

// FileDNSConfigurator manages DNS settings by directly modifying /etc/resolv.conf
type FileDNSConfigurator struct {
	originalState *DNSState
}

// NewFileDNSConfigurator creates a new file-based DNS configurator
func NewFileDNSConfigurator() (*FileDNSConfigurator, error) {
	return &FileDNSConfigurator{}, nil
}

// Name returns the configurator name
func (f *FileDNSConfigurator) Name() string {
	return "file-resolv.conf"
}

// SetDNS sets the DNS servers and returns the original servers
func (f *FileDNSConfigurator) SetDNS(servers []netip.Addr) ([]netip.Addr, error) {
	// Get current DNS settings before overriding
	originalServers, err := f.GetCurrentDNS()
	if err != nil {
		return nil, fmt.Errorf("get current DNS: %w", err)
	}

	// Backup original resolv.conf if not already backed up
	if !f.isBackupExists() {
		if err := f.backupResolvConf(); err != nil {
			return nil, fmt.Errorf("backup resolv.conf: %w", err)
		}
	}

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

	// Write new resolv.conf
	if err := f.writeResolvConf(servers); err != nil {
		return nil, fmt.Errorf("write resolv.conf: %w", err)
	}

	return originalServers, nil
}

// RestoreDNS restores the original DNS configuration
func (f *FileDNSConfigurator) RestoreDNS() error {
	if !f.isBackupExists() {
		return fmt.Errorf("no backup file exists")
	}

	// Copy backup back to original location
	if err := copyFile(resolvConfBackupPath, resolvConfPath); err != nil {
		return fmt.Errorf("restore from backup: %w", err)
	}

	// Remove backup file
	if err := os.Remove(resolvConfBackupPath); err != nil {
		return fmt.Errorf("remove backup file: %w", err)
	}

	return nil
}

// GetCurrentDNS returns the currently configured DNS servers
func (f *FileDNSConfigurator) GetCurrentDNS() ([]netip.Addr, error) {
	content, err := os.ReadFile(resolvConfPath)
	if err != nil {
		return nil, fmt.Errorf("read resolv.conf: %w", err)
	}

	return f.parseNameservers(string(content)), nil
}

// backupResolvConf creates a backup of the current resolv.conf
func (f *FileDNSConfigurator) backupResolvConf() error {
	// Get file info for permissions
	info, err := os.Stat(resolvConfPath)
	if err != nil {
		return fmt.Errorf("stat resolv.conf: %w", err)
	}

	if err := copyFile(resolvConfPath, resolvConfBackupPath); err != nil {
		return fmt.Errorf("copy file: %w", err)
	}

	// Preserve permissions
	if err := os.Chmod(resolvConfBackupPath, info.Mode()); err != nil {
		return fmt.Errorf("chmod backup: %w", err)
	}

	return nil
}

// writeResolvConf writes a new resolv.conf with the specified DNS servers
func (f *FileDNSConfigurator) writeResolvConf(servers []netip.Addr) error {
	if len(servers) == 0 {
		return fmt.Errorf("no DNS servers provided")
	}

	// Get file info for permissions
	info, err := os.Stat(resolvConfPath)
	if err != nil {
		return fmt.Errorf("stat resolv.conf: %w", err)
	}

	var content strings.Builder
	content.WriteString(resolvConfHeader)

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

	// Write the file
	if err := os.WriteFile(resolvConfPath, []byte(content.String()), info.Mode()); err != nil {
		return fmt.Errorf("write resolv.conf: %w", err)
	}

	return nil
}

// isBackupExists checks if a backup file exists
func (f *FileDNSConfigurator) isBackupExists() bool {
	_, err := os.Stat(resolvConfBackupPath)
	return err == nil
}

// parseNameservers extracts nameserver entries from resolv.conf content
func (f *FileDNSConfigurator) parseNameservers(content string) []netip.Addr {
	var servers []netip.Addr

	lines := strings.Split(content, "\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
}

// copyFile copies a file from src to dst
func copyFile(src, dst string) error {
	content, err := os.ReadFile(src)
	if err != nil {
		return fmt.Errorf("read source: %w", err)
	}

	// Get source file permissions
	info, err := os.Stat(src)
	if err != nil {
		return fmt.Errorf("stat source: %w", err)
	}

	if err := os.WriteFile(dst, content, info.Mode()); err != nil {
		return fmt.Errorf("write destination: %w", err)
	}

	return nil
}
