package main

import (
	"encoding/hex"
	"fmt"
	"log"
	"net"
	"os"
	"os/exec"
	"strings"

	"gopkg.in/yaml.v3"
)

type Config struct {
	Listen struct {
		Address string `yaml:"address"`
		Port    int    `yaml:"port"`
	} `yaml:"listen"`

	VirtualBox struct {
		VBoxManage string `yaml:"vboxmanage"`
	} `yaml:"virtualbox"`

	VMs []struct {
		Name string `yaml:"name"`
		MAC  string `yaml:"mac"`
	} `yaml:"vms"`
}

func normalizeMAC(mac string) ([]byte, error) {
	mac = strings.ReplaceAll(mac, ":", "")
	mac = strings.ReplaceAll(mac, "-", "")
	return hex.DecodeString(mac)
}

func isMagicPacket(data []byte, mac []byte) bool {
	if len(data) < 102 {
		return false
	}

	for i := 0; i < 6; i++ {
		if data[i] != 0xFF {
			return false
		}
	}

	for i := 0; i < 16; i++ {
		start := 6 + i*6
		if !strings.EqualFold(
			hex.EncodeToString(data[start:start+6]),
			hex.EncodeToString(mac),
		) {
			return false
		}
	}
	return true
}

func vmIsRunning(vboxmanage, vm string) bool {
	cmd := exec.Command(vboxmanage, "list", "runningvms")
	out, err := cmd.Output()
	if err != nil {
		return false
	}
	return strings.Contains(string(out), fmt.Sprintf("\"%s\"", vm))
}

func startVM(vboxmanage, vm string) {
	log.Printf("Starte VM: %s", vm)
	cmd := exec.Command(vboxmanage, "startvm", vm, "--type", "headless")
	err := cmd.Run()
	if err != nil {
		log.Printf("Fehler beim Start von %s: %v", vm, err)
	}
}

func main() {
	cfgData, err := os.ReadFile("config.yaml")
	if err != nil {
		log.Fatal("config.yaml nicht gefunden")
	}

	var cfg Config
	if err := yaml.Unmarshal(cfgData, &cfg); err != nil {
		log.Fatal("config.yaml ungültig")
	}

	type VMEntry struct {
		Name string
		MAC  []byte
	}

	var vms []VMEntry
	for _, vm := range cfg.VMs {
		mac, err := normalizeMAC(vm.MAC)
		if err != nil {
			log.Fatalf("Ungültige MAC: %s", vm.MAC)
		}
		vms = append(vms, VMEntry{Name: vm.Name, MAC: mac})
	}

	addr := fmt.Sprintf("%s:%d", cfg.Listen.Address, cfg.Listen.Port)
	conn, err := net.ListenPacket("udp", addr)
	if err != nil {
		log.Fatalf("UDP Listen fehlgeschlagen: %v", err)
	}
	defer conn.Close()

	log.Printf("WoL-Listener aktiv auf %s", addr)

	buf := make([]byte, 2048)

	for {
		n, _, err := conn.ReadFrom(buf)
		if err != nil {
			continue
		}

		packet := buf[:n]

		for _, vm := range vms {
			if isMagicPacket(packet, vm.MAC) {
				if vmIsRunning(cfg.VirtualBox.VBoxManage, vm.Name) {
					log.Printf("VM %s läuft bereits", vm.Name)
				} else {
					startVM(cfg.VirtualBox.VBoxManage, vm.Name)
				}
			}
		}
	}
}
