// client.go
package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io/fs"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"golang.org/x/sys/windows/registry"
)

type iaidInfo struct {
	Source string // Pfad oder Registry-Key
	IAID   uint32
}

// ---------- Linux ----------
var (
	reDhclientIAID = regexp.MustCompile(`\biaid\s+(\d+);`) // dhclient, NM
	reKVIAID       = regexp.MustCompile(`^IAID=(\d+)`)     // systemd-networkd
)

func iaidsLinux() ([]iaidInfo, error) {
	candidates := []string{
		"/var/lib/NetworkManager",
		"/var/lib/dhclient",         // manche Distros
		"/run/systemd/netif/leases", // systemd-networkd
		"/var/lib/dhcpcd", "/var/lib/dhcpcd5",
	}

	var out []iaidInfo

	visit := func(path string, d fs.DirEntry, err error) error {
		if err != nil || d.IsDir() {
			return nil
		}
		f, err := os.Open(path)
		if err != nil {
			return nil
		}
		defer f.Close()

		sc := bufio.NewScanner(f)
		for sc.Scan() {
			line := sc.Text()

			if m := reDhclientIAID.FindStringSubmatch(line); len(m) == 2 {
				if v, _ := strconv.ParseUint(m[1], 10, 32); v != 0 {
					out = append(out, iaidInfo{path, uint32(v)})
					break
				}
			}
			if m := reKVIAID.FindStringSubmatch(line); len(m) == 2 {
				if v, _ := strconv.ParseUint(m[1], 10, 32); v != 0 {
					out = append(out, iaidInfo{path, uint32(v)})
					break
				}
			}
		}
		return nil
	}

	for _, dir := range candidates {
		_ = filepath.WalkDir(dir, visit)
	}
	if len(out) == 0 {
		return nil, fmt.Errorf("keine IAID-Datei gefunden (root-Rechte?)")
	}
	return out, nil
}

// ---------- Windows ----------
/*func iaidsWindows() ([]iaidInfo, error) {
	base := `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces`
	root, err := registry.OpenKey(registry.LOCAL_MACHINE, base, registry.READ)
	if err != nil {
		return nil, err
	}
	defer root.Close()

	names, _ := root.ReadSubKeyNames(-1)
	var out []iaidInfo

	for _, n := range names {
		k, err := registry.OpenKey(root, n, registry.READ)
		if err != nil {
			continue
		}
		val, _, err := k.GetIntegerValue("IAID")
		k.Close()
		if err == nil {
			out = append(out, iaidInfo{
				Source: base + `\` + n + `\IAID`,
				IAID:   uint32(val),
			})
		}
	}
	if len(out) == 0 {
		return nil, fmt.Errorf("kein IAID-Wert in der Registry")
	}
	return out, nil
}*/

func iaidsWindows() ([]iaidInfo, error) {
	const base = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces`

	root, err := registry.OpenKey(registry.LOCAL_MACHINE, base, registry.READ)
	if err != nil {
		return nil, err
	}
	defer root.Close()

	var out []iaidInfo

	guidKeys, _ := root.ReadSubKeyNames(-1)
	for _, g := range guidKeys {
		k, err := registry.OpenKey(root, g, registry.READ)
		if err != nil {
			continue
		}

		if v, _, err := k.GetIntegerValue("Dhcpv6Iaid"); err == nil {
			out = append(out, iaidInfo{Source: base + `\` + g, IAID: uint32(v)})
			k.Close()
			continue
		}

		// --- Fallback: MAC → IAID (letzte 3 Bytes), falls DWORD fehlt ---
		if mac, _, err := k.GetBinaryValue("NetworkAddress"); err == nil && len(mac) >= 6 {
			iaid := uint32(mac[3])<<16 | uint32(mac[4])<<8 | uint32(mac[5])
			out = append(out, iaidInfo{Source: base + `\` + g + " (computed)", IAID: iaid})
		}
		k.Close()
	}
	if len(out) == 0 {
		return nil, fmt.Errorf("weder Dhcpv6Iaid noch MAC-basierten Fallback gefunden - nutzt der Client überhaupt DHCPv6?")
	}
	return out, nil
}

// ---------- Fallback: IAID selbst ableiten (MAC → 3 Byte) ----------
func iaidFromMAC(mac []byte, ifindex int) uint32 {
	if len(mac) >= 3 {
		return binary.BigEndian.Uint32([]byte{0, mac[len(mac)-3], mac[len(mac)-2], mac[len(mac)-1]})
	}
	// Notlösung: Interface-Index + 0xA0 im obersten Byte
	return uint32(0xA0000000 | (ifindex & 0x00FFFFFF))
}

/*######################################################################################################*/
/*######################################################################################################*/
/*######################################################################################################*/
/*######################################################################################################*/
/*######################################################################################################*/

// ---------- Datentyp, der an den Server geschickt wird ----------
type payload struct {
	Hostname string   `json:"hostname"`
	DUIDs    []string `json:"duids"` // hex-codiert
	IAIDs    []uint32
}

// ---------- Linux ----------
func duidsLinux() ([][]byte, error) {
	candidates := []string{
		"/var/lib/NetworkManager",
		"/var/lib/dhcp",
		"/var/lib/systemd/network",
		"/run/systemd/netif/leases",
	}
	var out [][]byte
	for _, dir := range candidates {
		_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
			if err != nil || d.IsDir() {
				return nil
			}
			buf, err := os.ReadFile(path)
			if err != nil {
				return nil
			}
			if duid := extractDUID(buf); len(duid) > 0 {
				out = append(out, duid)
			}
			return nil
		})
	}
	if len(out) == 0 {
		return nil, errors.New("keine DUID gefunden")
	}
	return out, nil
}

// ---------- Windows ----------
func duidsWindows() ([][]byte, error) {
	k, err := registry.OpenKey(registry.LOCAL_MACHINE,
		`SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters`,
		registry.READ)
	if err != nil {
		return nil, err
	}
	defer k.Close()

	val, _, err := k.GetBinaryValue("Dhcpv6DUID")
	if err != nil {
		return nil, err
	}
	return [][]byte{val}, nil
}

// ---------- Hilfsfunktionen ----------
func extractDUID(b []byte) []byte {
	if idx := bytes.Index(b, []byte("default-duid")); idx >= 0 {
		start := bytes.IndexByte(b[idx:], '"') + idx + 1
		end := bytes.IndexByte(b[start:], '"') + start
		hexEsc := bytes.ReplaceAll(b[start:end], []byte(`\x`), nil)
		duid, _ := hex.DecodeString(string(hexEsc))
		return duid
	}
	hexOnly := strings.TrimSpace(string(b))
	if len(hexOnly) >= 28 && allHex(hexOnly) { // plausibel?
		duid, _ := hex.DecodeString(hexOnly)
		return duid
	}
	return nil
}
func allHex(s string) bool {
	for _, c := range s {
		if !strings.ContainsRune("0123456789abcdefABCDEF", c) {
			return false
		}
	}
	return true
}

// ---------- main ----------
func main() {
	var raw [][]byte
	var all []iaidInfo
	var err error
	var err1 error
	switch runtime.GOOS {
	case "windows":
		raw, err = duidsWindows()
		all, err1 = iaidsWindows()
	default:
		raw, err = duidsLinux()
		all, err1 = iaidsLinux()
	}
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	}

	if err1 != nil {
		fmt.Fprintln(os.Stderr, err1)
	}

	for _, ia := range all {
		fmt.Printf("%-60s 0x%08x (%d)\n", ia.Source, ia.IAID, ia.IAID)
	}

	var gh []uint32

	for _, bh := range all {
		gh = append(gh, bh.IAID)
	}

	//host, _ := os.Hostname()
	host := "PC1020"
	p := payload{
		Hostname: host,
		DUIDs:    make([]string, len(raw)),
		IAIDs:    gh,
	}
	for i, d := range raw {
		p.DUIDs[i] = hex.EncodeToString(d)
	}

	// --- JSON kodieren ---
	body, _ := json.Marshal(p)

	// --- HTTP senden ---
	url := "http://localhost:8080/register" // <-- ggf. anpassen
	req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Fprintln(os.Stderr, "HTTP-Fehler:", err)
		os.Exit(1)
	}
	defer resp.Body.Close()
	fmt.Println("Server-Antwort:", resp.Status)
}
