//go:build windows

package main

import (
	"context"
	"encoding/json"
	"html/template"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/disk"
	"github.com/shirou/gopsutil/v3/host"
	"github.com/shirou/gopsutil/v3/mem"

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

type NetInterface struct {
	Name       string   `json:"name"`
	MAC        string   `json:"mac"`
	Addresses  []string `json:"addresses"`
	IsLoopback bool     `json:"is_loopback"`
	Profile    string   `json:"profile"`
}

type DiskInfo struct {
	Device      string  `json:"device"`
	Mountpoint  string  `json:"mountpoint"`
	Fstype      string  `json:"fstype"`
	Total       uint64  `json:"total"`
	Used        uint64  `json:"used"`
	Free        uint64  `json:"free"`
	UsedPercent float64 `json:"used_percent"`
	ReadOnly    bool    `json:"read_only"`
}

type Summary struct {
	Timestamp       time.Time      `json:"timestamp"`
	Hostname        string         `json:"hostname"`
	Username        string         `json:"username"`
	OS              string         `json:"os"`
	Platform        string         `json:"platform"`
	PlatformVersion string         `json:"platform_version"`
	KernelVersion   string         `json:"kernel_version"`
	Arch            string         `json:"arch"`
	UptimeSeconds   uint64         `json:"uptime_seconds"`
	BootTime        time.Time      `json:"boot_time"`
	Interfaces      []NetInterface `json:"interfaces"`
	CPUModel        string         `json:"cpu_model"`
	PhysicalCores   int            `json:"physical_cores"`
	LogicalCores    int            `json:"logical_cores"`
	CPUPercent      float64        `json:"cpu_percent"`
	MemoryTotal     uint64         `json:"memory_total"`
	MemoryUsed      uint64         `json:"memory_used"`
	MemoryFree      uint64         `json:"memory_free"`
	MemoryUsedPct   float64        `json:"memory_used_percent"`
	SwapTotal       uint64         `json:"swap_total"`
	SwapUsed        uint64         `json:"swap_used"`
	SwapFree        uint64         `json:"swap_free"`
	SwapUsedPct     float64        `json:"swap_used_percent"`
	Disks           []DiskInfo     `json:"disks"`
}

type InstalledApp struct {
	Name        string `json:"name"`
	Version     string `json:"version"`
	Publisher   string `json:"publisher"`
	InstallDate string `json:"install_date"`
	Source      string `json:"source"` // HKLM-64, HKLM-32, HKCU
}

var (
	mu           sync.RWMutex
	lastCPUUsage float64
)

func readUninstallKey(base registry.Key, path, src string) []InstalledApp {
	k, err := registry.OpenKey(base, path, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
	if err != nil {
		return nil
	}
	defer k.Close()

	names, err := k.ReadSubKeyNames(-1)
	if err != nil {
		return nil
	}

	apps := make([]InstalledApp, 0, len(names))
	for _, name := range names {
		sk, err := registry.OpenKey(k, name, registry.QUERY_VALUE)
		if err != nil {
			continue
		}

		// versteckte System-Komponenten ausblenden
		if v, _, err := sk.GetIntegerValue("SystemComponent"); err == nil && v == 1 {
			sk.Close()
			continue
		}

		displayName, _, _ := sk.GetStringValue("DisplayName")
		if strings.TrimSpace(displayName) == "" {
			sk.Close()
			continue
		}

		displayVersion, _, _ := sk.GetStringValue("DisplayVersion")
		publisher, _, _ := sk.GetStringValue("Publisher")
		installDate, _, _ := sk.GetStringValue("InstallDate")

		apps = append(apps, InstalledApp{
			Name:        displayName,
			Version:     displayVersion,
			Publisher:   publisher,
			InstallDate: installDate,
			Source:      src,
		})

		sk.Close()
	}

	return apps
}

func getInstalledApps() []InstalledApp {
	var all []InstalledApp

	// Maschinenweit 64-bit
	all = append(all,
		readUninstallKey(registry.LOCAL_MACHINE,
			`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, "HKLM-64")...,
	)

	// Maschinenweit 32-bit auf 64-bit
	all = append(all,
		readUninstallKey(registry.LOCAL_MACHINE,
			`SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall`, "HKLM-32")...,
	)

	// Aktueller Benutzer
	all = append(all,
		readUninstallKey(registry.CURRENT_USER,
			`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, "HKCU")...,
	)

	// Deduplizieren (Name+Version+Publisher)
	uniq := make(map[string]InstalledApp, len(all))
	for _, a := range all {
		key := strings.ToLower(strings.TrimSpace(a.Name) + "|" + strings.TrimSpace(a.Version) + "|" + strings.TrimSpace(a.Publisher))
		if _, ok := uniq[key]; !ok {
			uniq[key] = a
		}
	}

	out := make([]InstalledApp, 0, len(uniq))
	for _, v := range uniq {
		out = append(out, v)
	}
	sort.Slice(out, func(i, j int) bool { return strings.ToLower(out[i].Name) < strings.ToLower(out[j].Name) })
	return out
}

func appsHandler(w http.ResponseWriter, r *http.Request) {
	apps := getInstalledApps()
	w.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(w)
	enc.SetIndent("", "  ")
	_ = enc.Encode(apps)
}

// background sampler for CPU percentage to avoid blocking API calls
func startCPUSampler(ctx context.Context) {
	go func() {
		// Prime once
		_, _ = cpu.Percent(0, false)
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}

			pcts, err := cpu.Percent(time.Second, false)
			if err == nil && len(pcts) > 0 {
				mu.Lock()
				lastCPUUsage = pcts[0]
				mu.Unlock()
			}
		}
	}()
}

func getInterfaces() []NetInterface {
	ifaces, err := net.Interfaces()
	if err != nil {
		return nil
	}

	// Profile holen (best effort)
	profilesByIndex, _ := getWinNetProfiles()

	var out []NetInterface
	for _, ifc := range ifaces {
		if (ifc.Flags & net.FlagUp) == 0 {
			continue
		}
		addrs, _ := ifc.Addrs()
		var ips []string
		isLoop := (ifc.Flags & net.FlagLoopback) != 0
		for _, a := range addrs {
			var ip net.IP
			switch v := a.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ips = append(ips, ip.String())
		}
		if isLoop {
			continue
		}

		profile := profilesByIndex[ifc.Index] // 👈 jetzt sicher über Index

		out = append(out, NetInterface{
			Name:       ifc.Name,
			MAC:        ifc.HardwareAddr.String(),
			Addresses:  ips,
			IsLoopback: isLoop,
			Profile:    profile,
		})
	}
	return out
}

func getDisks() []DiskInfo {
	parts, err := disk.Partitions(true)
	if err != nil {
		return nil
	}
	var out []DiskInfo
	for _, p := range parts {
		du, err := disk.Usage(p.Mountpoint)
		if err != nil {
			continue
		}

		ro := false
		switch opts := any(p.Opts).(type) {
		case string:
			ro = strings.Contains(strings.ToLower(opts), "ro")
		case []string:
			for _, o := range opts {
				if strings.EqualFold(o, "ro") {
					ro = true
					break
				}
			}
		}

		out = append(out, DiskInfo{
			Device:      p.Device,
			Mountpoint:  p.Mountpoint,
			Fstype:      p.Fstype,
			Total:       du.Total,
			Used:        du.Used,
			Free:        du.Free,
			UsedPercent: du.UsedPercent,
			ReadOnly:    ro,
		})
	}
	return out
}

func collectSummary() Summary {
	hi, _ := host.Info()
	vm, _ := mem.VirtualMemory()
	sw, _ := mem.SwapMemory()
	coresPhys, _ := cpu.Counts(false)
	coresLog, _ := cpu.Counts(true)
	cpuInfos, _ := cpu.Info()
	username := ""
	if u, err := user.Current(); err == nil {
		username = u.Username
	} else {
		username = os.Getenv("USERNAME")
	}
	model := ""
	if len(cpuInfos) > 0 {
		model = cpuInfos[0].ModelName
	}

	mu.RLock()
	cpuPct := lastCPUUsage
	mu.RUnlock()

	var boot time.Time
	if hi != nil && hi.BootTime > 0 {
		boot = time.Unix(int64(hi.BootTime), 0)
	}

	return Summary{
		Timestamp:       time.Now(),
		Hostname:        safe(hi, func(h *host.InfoStat) string { return h.Hostname }),
		Username:        username,
		OS:              safe(hi, func(h *host.InfoStat) string { return h.OS }),
		Platform:        safe(hi, func(h *host.InfoStat) string { return h.Platform }),
		PlatformVersion: safe(hi, func(h *host.InfoStat) string { return h.PlatformVersion }),
		KernelVersion:   safe(hi, func(h *host.InfoStat) string { return h.KernelVersion }),
		Arch:            runtime.GOARCH,
		UptimeSeconds:   safe(hi, func(h *host.InfoStat) uint64 { return h.Uptime }),
		BootTime:        boot,
		Interfaces:      getInterfaces(),
		CPUModel:        model,
		PhysicalCores:   coresPhys,
		LogicalCores:    coresLog,
		CPUPercent:      cpuPct,
		MemoryTotal:     vm.Total,
		MemoryUsed:      vm.Used,
		MemoryFree:      vm.Free,
		MemoryUsedPct:   vm.UsedPercent,
		SwapTotal:       sw.Total,
		SwapUsed:        sw.Used,
		SwapFree:        sw.Free,
		SwapUsedPct:     sw.UsedPercent,
		Disks:           getDisks(),
	}
}

func safe[T any, R any](v T, f func(T) R) R {
	if any(v) == nil {
		var zero R
		return zero
	}
	return f(v)
}

var page = template.Must(template.New("index").Parse(`<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Windows Systeminfo</title>
<style>
  :root {
    --bg: #f5f6f8;
    --fg: #121826;
    --muted: #5d6b82;
    --card: #ffffff;
    --accent: #0066ff;
    --border: rgba(12, 21, 37, 0.08);
  }
  *{box-sizing:border-box}
  body{
    max-width:1200px;
    margin:0 auto;
    padding:24px;
    font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;
    background:var(--bg);
    color:var(--fg);
  }
  h1{font-size:22px;margin:0 0 6px 0}
  h2{margin:0 0 16px 0}
  .grid{display:flex;flex-direction:column;gap:12px}
  .card{
    width:100%;
    background:var(--card);
    border:1px solid var(--border);
    border-radius:16px;
    padding:14px 16px 10px 16px;
    box-shadow:0 10px 30px rgba(15,23,42,0.03);
  }
  .k{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px}
  .v{font-weight:600}
  .row{display:flex;justify-content:space-between;gap:8px;align-items:center;margin:6px 0}
  .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}
  .stereo{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color: #ff0000ff}
  .pill{
    display:inline-block;
    background:#e8f1ff;
    color:#0047cc;
    border-radius:999px;
    padding:2px 8px;
    margin:2px 6px 0 0;
    font-size:14px;
  }
  .pill2{
    display:inline-block;
    background:#ffe480;
    color:#3e3e3e;
    border-radius:999px;
    padding:2px 8px;
    margin:2px 6px 0 0;
    font-size:14px;
  }
  .bar{
    height:10px;
    background:#edf0f5;
    border-radius:999px;
    overflow:hidden;
  }
  .fill{
    height:100%;
    background:var(--accent);
    width:0%;
    transition:width .3s ease-out;
  }
  footer{margin-top:18px;color:var(--muted);font-size:12px}
  .disks table{width:100%;border-collapse:collapse}
  .disks th,.disks td{
    padding:8px;
    border-bottom:1px solid #e5e9f2;
    text-align:left;
    font-size:14px
  }
  .disks th{background:#f5f6f8;font-weight:500}
  button#btnLoadApps{
    border:none;
    background:#e8f1ff;
    color:#0047cc;
    font-weight:500;
  }
  input#appsFilter{
    background:#ffffff;
    color:var(--fg);
    border:1px solid #d0d7e2;
    border-radius:8px;
  }
  table thead th{font-size:13px;color:#3b4560}
  @media (prefers-color-scheme: dark) {
    /* optional: falls Host dunkles Theme erzwingt */
    :root {
      --bg:#f5f6f8;
      --fg:#121826;
      --card:#ffffff;
    }
  }
</style>
</head>
<body>
  <h1>Windows Systeminfo</h1>
  <h2><div class="v stereo" id="hostname2"></div></h2>
  <div class="grid">
    <div class="card">
      <div class="k">Host</div>
      <div class="row"><div>Hostname</div><div class="v stereo" id="hostname"></div></div>
      <div class="row"><div>User</div><div class="v mono" id="username"></div></div>
      <div class="row"><div>Uptime</div><div class="v" id="uptime"></div></div>
      <div class="row"><div>Boot</div><div class="v" id="boottime"></div></div>
      <div class="row"><div>Arch</div><div class="v mono" id="arch"></div></div>
    </div>

    <div class="card">
      <div class="k">OS</div>
      <div class="row"><div>Betriebssystem</div><div class="v" id="os"></div></div>
      <div class="row"><div>Platform</div><div class="v" id="platform"></div></div>
      <div class="row"><div>Kernel</div><div class="v mono" id="kernel"></div></div>
      <div class="row"><div>Stand</div><div class="v" id="ts"></div></div>
    </div>

    <div class="card">
      <div class="k">CPU</div>
      <div class="row"><div>Modell</div><div class="v" id="cpu_model"></div></div>
      <div class="row"><div>Kerne (phys/log)</div><div class="v" id="cores"></div></div>
      <div class="row"><div>Auslastung</div><div class="v" id="cpu_pct"></div></div>
      <div class="bar"><div class="fill" id="cpu_bar"></div></div>
    </div>

    <div class="card">
      <div class="k">Speicher</div>
      <div class="row"><div>RAM</div><div class="v" id="ram"></div></div>
      <div class="bar"><div class="fill" id="ram_bar"></div></div>
      <div class="row" style="margin-top:8px;"><div>Swap</div><div class="v" id="swap"></div></div>
      <div class="bar"><div class="fill" id="swap_bar"></div></div>
    </div>

    <div class="card">
      <div class="k">Netzwerk</div>
      <div id="ifaces"></div>
    </div>

    <div class="card disks">
      <div class="k">Laufwerke</div>
      <table>
        <thead><tr><th>Mount</th><th>Typ</th><th>Belegt</th><th>Gesamt</th><th>%</th></tr></thead>
        <tbody id="disks"></tbody>
      </table>
    </div>
    <div class="card">
      <div class="k">Installierte Programme</div>
      <div class="row" style="gap:8px;align-items:center;">
        <button id="btnLoadApps" class="pill" style="cursor:pointer;">Laden</button>
        <input id="appsFilter" type="text" placeholder="Suchen …"
          style="flex:1;padding:6px 10px;">
        <span class="k" id="appsCount"></span>
      </div>
      <div class="apps">
        <table style="width:100%;border-collapse:collapse">
        <thead>
          <tr>
          <th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Name</th>
          <th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Version</th>
          <th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Publisher</th>
          <th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Quelle</th>
          </tr>
        </thead>
        <tbody id="apps"></tbody>
        </table>
      </div>
    </div>
  </div>

  <footer>Aktualisiert alle 2s</footer>

<script>
function bytes(n){ if(n===0||n===undefined) return "0 B"; const u=['B','KB','MB','GB','TB','PB']; let i=Math.floor(Math.log(n)/Math.log(1024)); i=Math.min(i,u.length-1); return (n/Math.pow(1024,i)).toFixed(1)+" "+u[i]; }
function pct(p){ return (p||0).toFixed(1)+"%"; }
function hms(sec){ sec = Math.floor(sec||0); const d=Math.floor(sec/86400); sec%=86400; const h=Math.floor(sec/3600); sec%=3600; const m=Math.floor(sec/60); const s=sec%60; let out=[]; if(d) out.push(d+"d"); if(h) out.push(h+"h"); if(m) out.push(m+"m"); out.push(s+"s"); return out.join(" "); }

async function load(){
  try{
    const r = await fetch('/api/summary');
    const j = await r.json();

    document.getElementById('hostname').textContent = j.hostname || '-';
    document.getElementById('hostname2').textContent = j.hostname || '-';
    document.getElementById('username').textContent = j.username || '-';
    document.getElementById('uptime').textContent   = hms(j.uptime_seconds);
    document.getElementById('boottime').textContent = j.boot_time ? new Date(j.boot_time).toLocaleString() : '-';
    document.getElementById('arch').textContent     = j.arch || '-';

    document.getElementById('os').textContent       = (j.os||'') ;
    document.getElementById('platform').textContent = (j.platform||'') + (j.platform_version?(" "+j.platform_version):"");
    document.getElementById('kernel').textContent   = j.kernel_version || '-';
    document.getElementById('ts').textContent       = j.timestamp ? new Date(j.timestamp).toLocaleString() : '-';

    document.getElementById('cpu_model').textContent= j.cpu_model || '-';
    document.getElementById('cores').textContent    = (j.physical_cores||0) + " / " + (j.logical_cores||0);
    document.getElementById('cpu_pct').textContent  = pct(j.cpu_percent);
    document.getElementById('cpu_bar').style.width  = (j.cpu_percent||0)+"%";

    document.getElementById('ram').textContent      = bytes(j.memory_used)+" / "+bytes(j.memory_total)+" ("+pct(j.memory_used_percent)+")";
    document.getElementById('ram_bar').style.width  = (j.memory_used_percent||0)+"%";

    document.getElementById('swap').textContent     = bytes(j.swap_used)+" / "+bytes(j.swap_total)+" ("+pct(j.swap_used_percent)+")";
    document.getElementById('swap_bar').style.width = (j.swap_used_percent||0)+"%";

    const wrap = document.getElementById('ifaces'); wrap.innerHTML='';
    (j.interfaces||[]).forEach(n=>{
      const div = document.createElement('div');
      div.innerHTML = '<div class="row"><div>'+n.name+'</div><div class="mono">'+(n.mac||'-')+'</div></div>';
      if (n.profile) {
        const p = document.createElement('span');
        p.className = 'pill2';
        p.textContent = n.profile;
        div.appendChild(p);
      }
      (n.addresses||[]).forEach(ip=>{ const span=document.createElement('span'); span.className='pill mono'; span.textContent=ip; div.appendChild(span); });
      wrap.appendChild(div);
    });

    const tbody = document.getElementById('disks'); tbody.innerHTML='';
    (j.disks||[]).forEach(d=>{
      const tr = document.createElement('tr');
      tr.innerHTML = '<td class="mono">'+d.mountpoint+'</td><td>'+d.fstype+'</td><td>'+bytes(d.used)+'</td><td>'+bytes(d.total)+'</td><td>'+pct(d.used_percent)+'</td>';
      tbody.appendChild(tr);
    });
  }catch(e){ console.error(e); }
}

load();
setInterval(load, 2000);

let appsCache = null;

function renderApps(list){
  const tbody = document.getElementById('apps');
  tbody.innerHTML = '';
  (list||[]).forEach(a=>{
    const tr = document.createElement('tr');
    tr.innerHTML =
      '<td class="mono" style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.name||'-')+'</td>'+
      '<td style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.version||'')+'</td>'+
      '<td style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.publisher||'')+'</td>'+
      '<td style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.source||'')+'</td>';
    tbody.appendChild(tr);
  });
  const c = document.getElementById('appsCount');
  if(c) c.textContent = (list||[]).length+' Einträge';
}

async function loadAppsOnce(){
  if(appsCache) return;
  try{
    const r = await fetch('/api/apps');
    appsCache = await r.json();
    renderApps(appsCache);
  }catch(e){
    console.error(e);
  }
}

document.addEventListener('DOMContentLoaded', ()=>{
  const btn = document.getElementById('btnLoadApps');
  const inp = document.getElementById('appsFilter');
  if(btn){ btn.addEventListener('click', loadAppsOnce); }
  if(inp){
    inp.addEventListener('input', ()=>{
      if(!appsCache) return;
      const q = inp.value.toLowerCase();
      const filtered = appsCache.filter(a =>
        (a.name||'').toLowerCase().includes(q) ||
        (a.version||'').toLowerCase().includes(q) ||
        (a.publisher||'').toLowerCase().includes(q) ||
        (a.source||'').toLowerCase().includes(q)
      );
      renderApps(filtered);
    });
  }
});
</script>
</body>
</html>
`))

func indexHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := page.Execute(w, nil); err != nil {
		http.Error(w, err.Error(), 500)
	}
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	sum := collectSummary()
	w.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(w)
	enc.SetIndent("", "  ")
	if err := enc.Encode(sum); err != nil {
		http.Error(w, err.Error(), 500)
	}
}

// --- ab hier: Dienst-Integration ---

const serviceName = "GoSysInfoService"

// runHTTP startet deinen bisherigen HTTP-Server und reagiert auf ctx.Done()
func runHTTP(ctx context.Context) error {
	mux := http.NewServeMux()
	mux.HandleFunc("/", indexHandler)
	mux.HandleFunc("/api/apps", appsHandler)
	mux.HandleFunc("/api/summary", apiHandler)

	addr := ":24000"

	srv := &http.Server{
		Addr:    addr,
		Handler: mux,
	}

	// CPU-Sampler an das gleiche ctx hängen
	startCPUSampler(ctx)

	log.Printf("Starte lokales Dashboard auf http://%s … (nur von diesem Rechner erreichbar)", addr)

	// Shutdown-Goroutine
	go func() {
		<-ctx.Done()
		shCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		_ = srv.Shutdown(shCtx)
		cancel() // explizit aufrufen, kein defer in der Goroutine nötig
	}()

	// Blockiert hier bis Stop oder Fehler
	err := srv.ListenAndServe()
	// ListenAndServe gibt bei Shutdown typischerweise http.ErrServerClosed zurück
	if err == http.ErrServerClosed {
		return nil
	}
	return err
}

// windows-svc Wrapper
type winService struct{}

func (s *winService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {
	const accepted = svc.AcceptStop | svc.AcceptShutdown
	status <- svc.Status{State: svc.StartPending}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // <-- neu: für alle Rückgabepfade

	go func() {
		if err := runHTTP(ctx); err != nil {
			log.Printf("HTTP server stopped: %v", err)
		}
	}()

	status <- svc.Status{State: svc.Running, Accepts: accepted}

	for c := range r {
		switch c.Cmd {
		case svc.Interrogate:
			status <- c.CurrentStatus
		case svc.Stop, svc.Shutdown:
			status <- svc.Status{State: svc.StopPending}
			return false, 0 // cancel() läuft trotzdem dank defer
		}
	}

	// falls der Channel mal zu ist
	return false, 0
}

func main() {
	// Prüfen, ob wir als Windows-Dienst laufen
	isService, err := svc.IsWindowsService()
	if err != nil {
		log.Fatalf("svc.IsWindowsService: %v", err)
	}

	if isService {
		// Hier spricht das Programm mit dem Service Control Manager
		if err := svc.Run(serviceName, &winService{}); err != nil {
			log.Fatalf("svc.Run failed: %v", err)
		}
		return
	}

	// normaler Konsolenmodus
	ctx := context.Background()
	if err := runHTTP(ctx); err != nil {
		log.Fatal(err)
	}
}

type psConnProfile struct {
	Name            string `json:"Name"`
	InterfaceAlias  string `json:"InterfaceAlias"`
	InterfaceIndex  int    `json:"InterfaceIndex"`
	NetworkCategory string `json:"NetworkCategory"` // "Public", "Private", "DomainAuthenticated"
}

func getWinNetProfiles() (map[int]string, error) {
	cmd := exec.Command(
		"powershell",
		"-NoProfile",
		"-NonInteractive",
		"-Command",
		`Get-NetConnectionProfile | Select-Object InterfaceAlias,InterfaceIndex,NetworkCategory | ConvertTo-Json -Depth 3`,
	)

	out, err := cmd.Output()
	if err != nil {
		log.Printf("getWinNetProfiles: powershell error: %v", err)
		return nil, err
	}
	if len(out) == 0 {
		return map[int]string{}, nil
	}

	// Wir wissen nicht, ob PS ein Objekt oder ein Array zurückgibt → erst Array versuchen
	type psConnProfile struct {
		InterfaceAlias  string      `json:"InterfaceAlias"`
		InterfaceIndex  int         `json:"InterfaceIndex"`
		NetworkCategory interface{} `json:"NetworkCategory"`
	}

	normalizeCat := func(v interface{}) string {
		switch vv := v.(type) {
		case string:
			// "Public", "Private", "DomainAuthenticated"
			return vv
		case float64:
			switch int(vv) {
			case 0:
				return "Public"
			case 1:
				return "Private"
			case 2:
				return "DomainAuthenticated"
			default:
				return "Unknown"
			}
		default:
			return ""
		}
	}

	// 1) Array probieren
	var arr []psConnProfile
	if err := json.Unmarshal(out, &arr); err == nil {
		res := make(map[int]string, len(arr))
		for _, p := range arr {
			res[p.InterfaceIndex] = normalizeCat(p.NetworkCategory)
		}
		return res, nil
	}

	// 2) Einzelnes Objekt probieren
	var single psConnProfile
	if err := json.Unmarshal(out, &single); err != nil {
		log.Printf("getWinNetProfiles: cannot unmarshal: %v -- out: %s", err, string(out))
		return nil, err
	}
	res := make(map[int]string, 1)
	res[single.InterfaceIndex] = normalizeCat(single.NetworkCategory)
	return res, nil
}
