package collector

import (
	"os/exec"
	"strconv"
	"strings"
)

type NetworkInterface struct {
	Name    string `json:"name"`
	Role    string `json:"role"`
	IP      string `json:"ip"`
	MAC     string `json:"mac"`
	Status  string `json:"status"`
	RxBytes int64  `json:"rx_bytes"`
	TxBytes int64  `json:"tx_bytes"`
}

func CollectNetwork() []NetworkInterface {
	ifaces := parseIfconfig()
	addTrafficStats(ifaces)
	return ifaces
}

// parseIfconfig parst ifconfig -a Output
func parseIfconfig() []NetworkInterface {
	out, err := exec.Command("ifconfig", "-a").Output()
	if err != nil {
		return nil
	}

	var ifaces []NetworkInterface
	var current *NetworkInterface

	for _, line := range strings.Split(string(out), "\n") {
		// Neues Interface: "igb0: flags=..."
		if len(line) > 0 && line[0] != '\t' && line[0] != ' ' && strings.Contains(line, ": flags=") {
			if current != nil {
				ifaces = append(ifaces, *current)
			}
			name := strings.Split(line, ":")[0]

			// Loopback und virtuelle Interfaces ueberspringen
			if name == "lo0" || strings.HasPrefix(name, "pflog") || strings.HasPrefix(name, "pfsync") || strings.HasPrefix(name, "enc") {
				current = nil
				continue
			}

			status := "down"
			if strings.Contains(line, "UP") {
				status = "up"
			}

			current = &NetworkInterface{
				Name:   name,
				Status: status,
				Role:   guessRole(name),
			}
			continue
		}

		if current == nil {
			continue
		}

		trimmed := strings.TrimSpace(line)

		// IPv4 Adresse
		if strings.HasPrefix(trimmed, "inet ") && !strings.HasPrefix(trimmed, "inet6") {
			fields := strings.Fields(trimmed)
			if len(fields) >= 2 {
				current.IP = fields[1]
			}
		}

		// MAC Adresse
		if strings.HasPrefix(trimmed, "ether ") {
			fields := strings.Fields(trimmed)
			if len(fields) >= 2 {
				current.MAC = fields[1]
			}
		}
	}

	if current != nil {
		ifaces = append(ifaces, *current)
	}

	return ifaces
}

// addTrafficStats fuegt RX/TX Bytes via netstat -ib hinzu
func addTrafficStats(ifaces []NetworkInterface) {
	out, err := exec.Command("/usr/bin/netstat", "-ib").Output()
	if err != nil {
		return
	}

	// Nur <Link#> Zeilen haben die totalen Byte-Zaehler
	// Format variiert, aber die letzten 4 Felder sind immer: Opkts Oerrs Obytes Coll
	// und davor: Ipkts Ierrs Idrop Ibytes
	lines := strings.Split(string(out), "\n")
	for _, line := range lines[1:] {
		if !strings.Contains(line, "<Link#") {
			continue
		}
		fields := strings.Fields(line)
		if len(fields) < 11 {
			continue
		}
		name := fields[0]
		// Von hinten zaehlen: Coll=last, Obytes=last-1, Oerrs=last-2, Opkts=last-3
		// Ibytes=last-4 von Opkts aus: Ibytes ist 4 vor Opkts
		n := len(fields)
		obytes, err1 := strconv.ParseInt(fields[n-2], 10, 64)
		// Ibytes: 7 von hinten (Coll, Obytes, Oerrs, Opkts, Ibytes, Idrop, Ierrs)
		ibytes, err2 := strconv.ParseInt(fields[n-5], 10, 64)
		if err1 != nil || err2 != nil {
			continue
		}

		for i := range ifaces {
			if ifaces[i].Name == name {
				ifaces[i].RxBytes = ibytes
				ifaces[i].TxBytes = obytes
			}
		}
	}
}

// guessRole versucht die Rolle anhand des Interface-Namens zu erraten
func guessRole(name string) string {
	// OPNsense benutzt typischerweise config.xml fuer Zuordnungen
	// Hier: Heuristik basierend auf gaengigen Konventionen
	lower := strings.ToLower(name)

	// Versuche OPNsense-Config zu lesen
	role := getOPNsenseIfRole(name)
	if role != "" {
		return role
	}

	// Fallback-Heuristik
	if strings.Contains(lower, "wan") {
		return "WAN"
	}
	if strings.Contains(lower, "lan") {
		return "LAN"
	}
	if strings.HasPrefix(lower, "igb0") || strings.HasPrefix(lower, "em0") || strings.HasPrefix(lower, "ix0") {
		return "WAN"
	}
	if strings.HasPrefix(lower, "igb1") || strings.HasPrefix(lower, "em1") || strings.HasPrefix(lower, "ix1") {
		return "LAN"
	}

	return "OPT"
}

// getOPNsenseIfRole liest die Interface-Rolle aus der OPNsense config.xml
func getOPNsenseIfRole(ifName string) string {
	// config.xml Struktur: <interfaces><wan><if>vtnet1</if>...
	// Wir suchen nach <if>IFNAME</if> und schauen welcher Block (wan/lan/opt) es enthaelt
	out, err := exec.Command("grep", "-B5", "<if>"+ifName+"</if>", "/conf/config.xml").Output()
	if err != nil {
		return ""
	}

	content := string(out)
	// Suche nach dem oeffnenden Tag des Parent-Elements
	for _, role := range []string{"wan", "lan", "opt1", "opt2", "opt3", "opt4", "opt5", "opt6"} {
		if strings.Contains(content, "<"+role+">") {
			return strings.ToUpper(role)
		}
	}

	return ""
}
