package platform

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"sort"
	"strings"
)

func (s *System) ListInterfaces() ([]InterfaceInfo, error) {
	names, err := listInterfaceNames()
	if err != nil {
		return nil, err
	}

	out := make([]InterfaceInfo, 0, len(names))
	for _, name := range names {
		state := "unknown"
		if raw, err := exec.Command("ip", "-o", "link", "show", name).Output(); err == nil {
			fields := strings.Fields(string(raw))
			if len(fields) >= 9 {
				state = fields[8]
			}
		}

		var ipv4 []string
		if raw, err := exec.Command("ip", "-o", "-4", "addr", "show", "dev", name).Output(); err == nil {
			for _, line := range strings.Split(strings.TrimSpace(string(raw)), "\n") {
				fields := strings.Fields(line)
				if len(fields) >= 4 {
					ipv4 = append(ipv4, fields[3])
				}
			}
		}

		out = append(out, InterfaceInfo{Name: name, State: state, IPv4: ipv4})
	}

	return out, nil
}

func (s *System) DefaultRoute() string {
	raw, err := exec.Command("ip", "route", "show", "default").Output()
	if err != nil {
		return ""
	}
	fields := strings.Fields(string(raw))
	for i := 0; i < len(fields)-1; i++ {
		if fields[i] == "via" {
			return fields[i+1]
		}
	}
	return ""
}

func (s *System) DHCPOne(iface string) (string, error) {
	var out bytes.Buffer
	if err := exec.Command("ip", "link", "set", iface, "up").Run(); err != nil {
		fmt.Fprintf(&out, "WARN: ip link set up failed: %v\n", err)
	}
	if raw, err := exec.Command("dhclient", "-r", iface).CombinedOutput(); err == nil {
		out.Write(raw)
	} else if len(raw) > 0 {
		out.Write(raw)
	}
	raw, err := exec.Command("dhclient", "-4", "-v", iface).CombinedOutput()
	out.Write(raw)
	if err != nil {
		return out.String(), err
	}
	return out.String(), nil
}

func (s *System) DHCPAll() (string, error) {
	ifaces, err := listInterfaceNames()
	if err != nil {
		return "", err
	}
	var out strings.Builder
	for _, iface := range ifaces {
		fmt.Fprintf(&out, "[%s]\n", iface)
		log, err := s.DHCPOne(iface)
		out.WriteString(log)
		if err != nil {
			fmt.Fprintf(&out, "ERROR: %v\n", err)
		}
		out.WriteString("\n")
	}
	return out.String(), nil
}

func (s *System) SetStaticIPv4(cfg StaticIPv4Config) (string, error) {
	if cfg.Interface == "" || cfg.Address == "" || cfg.Prefix == "" {
		return "", fmt.Errorf("interface, address, and prefix are required")
	}

	dns := cfg.DNS
	if len(dns) == 0 {
		dns = []string{"77.88.8.8", "77.88.8.1", "1.1.1.1", "8.8.8.8"}
	}

	var out strings.Builder
	_ = exec.Command("ip", "link", "set", cfg.Interface, "up").Run()
	_ = exec.Command("ip", "addr", "flush", "dev", cfg.Interface).Run()
	if raw, err := exec.Command("ip", "addr", "add", cfg.Address+"/"+cfg.Prefix, "dev", cfg.Interface).CombinedOutput(); err != nil {
		return string(raw), err
	}
	out.WriteString("address configured\n")
	if cfg.Gateway != "" {
		_ = exec.Command("ip", "route", "del", "default").Run()
		if raw, err := exec.Command("ip", "route", "add", "default", "via", cfg.Gateway, "dev", cfg.Interface).CombinedOutput(); err != nil {
			return out.String() + string(raw), err
		}
		out.WriteString("default route configured\n")
	}

	var resolv strings.Builder
	for _, dnsServer := range dns {
		dnsServer = strings.TrimSpace(dnsServer)
		if dnsServer == "" {
			continue
		}
		fmt.Fprintf(&resolv, "nameserver %s\n", dnsServer)
	}
	if err := os.WriteFile("/etc/resolv.conf", []byte(resolv.String()), 0644); err != nil {
		return out.String(), err
	}
	out.WriteString("dns configured\n")
	return out.String(), nil
}

// SetInterfaceState brings a network interface up or down.
func (s *System) SetInterfaceState(iface string, up bool) error {
	state := "down"
	if up {
		state = "up"
	}
	return exec.Command("ip", "link", "set", "dev", iface, state).Run()
}

// GetInterfaceState returns true if the interface is UP.
func (s *System) GetInterfaceState(iface string) (bool, error) {
	raw, err := os.ReadFile(fmt.Sprintf("/sys/class/net/%s/operstate", iface))
	if err != nil {
		return false, err
	}
	state := strings.TrimSpace(string(raw))
	return state == "up", nil
}

func listInterfaceNames() ([]string, error) {
	raw, err := exec.Command("ip", "-o", "link", "show").Output()
	if err != nil {
		return nil, err
	}
	var out []string
	for _, line := range strings.Split(strings.TrimSpace(string(raw)), "\n") {
		fields := strings.SplitN(line, ": ", 3)
		if len(fields) < 2 {
			continue
		}
		name := fields[1]
		if name == "lo" || strings.HasPrefix(name, "docker") || strings.HasPrefix(name, "virbr") ||
			strings.HasPrefix(name, "veth") || strings.HasPrefix(name, "tun") ||
			strings.HasPrefix(name, "tap") || strings.HasPrefix(name, "br-") ||
			strings.HasPrefix(name, "bond") || strings.HasPrefix(name, "dummy") {
			continue
		}
		out = append(out, name)
	}
	sort.Strings(out)
	return out, nil
}
